myman 0.0.6__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.
- myman-0.0.6/PKG-INFO +72 -0
- myman-0.0.6/README.md +62 -0
- myman-0.0.6/pyproject.toml +25 -0
- myman-0.0.6/setup.cfg +4 -0
- myman-0.0.6/src/myman/__init__.py +0 -0
- myman-0.0.6/src/myman/load_data.py +49 -0
- myman-0.0.6/src/myman/myman.py +141 -0
- myman-0.0.6/src/myman/output_formatter.py +51 -0
- myman-0.0.6/src/myman.egg-info/PKG-INFO +72 -0
- myman-0.0.6/src/myman.egg-info/SOURCES.txt +12 -0
- myman-0.0.6/src/myman.egg-info/dependency_links.txt +1 -0
- myman-0.0.6/src/myman.egg-info/requires.txt +3 -0
- myman-0.0.6/src/myman.egg-info/top_level.txt +1 -0
- myman-0.0.6/tests/test_myman.py +25 -0
myman-0.0.6/PKG-INFO
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: myman
|
|
3
|
+
Version: 0.0.6
|
|
4
|
+
Summary: Draw from Sequence of 'My Man' Posts by Kevin Kruse
|
|
5
|
+
Author-email: Dirk Eddelbuettel <edd@debian.org>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Provides-Extra: test
|
|
9
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## myman: Sequence of posts by Kevin Kruse starting with 'My man ...'
|
|
13
|
+
|
|
14
|
+
[](https://github.com/eddelbuettel/myman-py/actions?query=workflow%3Aci)
|
|
15
|
+
[](https://opensource.org/license/gpl-2.0)
|
|
16
|
+
[](https://github.com/eddelbuettel/myman-py)
|
|
17
|
+
|
|
18
|
+
### Motivation
|
|
19
|
+
|
|
20
|
+
Starting on the afternoon of July 17, 2026, and lasting for one initial week, Kevin Kruse fired off
|
|
21
|
+
an astonishing array of over six hundred ninety eight BlueSky replies to [an initial post of
|
|
22
|
+
his][postone] featuring a certain government figure. On August 12, 2026, a second wave started [with
|
|
23
|
+
this post][posttwo] aiming at another government figure. (There were also two stray posts from the
|
|
24
|
+
interim period.) This was followed on August 17, 2026, with another series starting with [this
|
|
25
|
+
post][postthree]. A fourth wave started on August 27, 2026, with [this post][postfour]. A fifth
|
|
26
|
+
wave started on August 30, 2026, with [this post][postfive]. A sixth wave started on September 4,
|
|
27
|
+
2026 with [this post][postsix]. The total now stands at one thousand three hundred ninety four
|
|
28
|
+
posts.
|
|
29
|
+
|
|
30
|
+
All posts start with "My man ..." and make for excellent input to a `fortunes`-like package. So this
|
|
31
|
+
small package obliges and offers a random draw each time its `myman()` function is called. This
|
|
32
|
+
Python package benefits from the corresponding [R package][rpkg] and uses the csv file created and
|
|
33
|
+
packaged there.
|
|
34
|
+
|
|
35
|
+
The Python package is equivalent to the R in all aspects but one: each call will only return one
|
|
36
|
+
post.
|
|
37
|
+
|
|
38
|
+
### Example
|
|
39
|
+
|
|
40
|
+
The simple file `main.py` illustrates three calling examples with (numeric) index, target, or
|
|
41
|
+
topical index.
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
$ ./main.py
|
|
45
|
+
My man looks like Pat Boone being tased.
|
|
46
|
+
-- about Stephen Miller on 2026-07-16
|
|
47
|
+
|
|
48
|
+
My man looks like a superhero whose power is Bryl Cream.
|
|
49
|
+
-- about Scott Bessent on 2026-08-31
|
|
50
|
+
|
|
51
|
+
My man looks like he's asked the maitre'd to remove a party of \
|
|
52
|
+
four he finds visually unpleasant.
|
|
53
|
+
-- about Scott Bessent on 2026-08-30
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Author
|
|
58
|
+
|
|
59
|
+
Dirk Eddelbuettel
|
|
60
|
+
|
|
61
|
+
### License
|
|
62
|
+
|
|
63
|
+
GPL (>= 2)
|
|
64
|
+
|
|
65
|
+
[postone]: https://bsky.app/profile/did:plc:cnpe7qvcyjrhm6w7w7e4atur/post/3mqum4mxsuk2g
|
|
66
|
+
[posttwo]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3mstvbjpagca2
|
|
67
|
+
[postthree]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3mtcpiw7gi22j
|
|
68
|
+
[postfour]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3mu3pugs2yk2f
|
|
69
|
+
[postfive]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3mudbzy5ksk25
|
|
70
|
+
[postsix]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3muparqtdkk2w
|
|
71
|
+
|
|
72
|
+
[rpkg]: https://github.com/eddelbuettel/myman
|
myman-0.0.6/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
|
|
2
|
+
## myman: Sequence of posts by Kevin Kruse starting with 'My man ...'
|
|
3
|
+
|
|
4
|
+
[](https://github.com/eddelbuettel/myman-py/actions?query=workflow%3Aci)
|
|
5
|
+
[](https://opensource.org/license/gpl-2.0)
|
|
6
|
+
[](https://github.com/eddelbuettel/myman-py)
|
|
7
|
+
|
|
8
|
+
### Motivation
|
|
9
|
+
|
|
10
|
+
Starting on the afternoon of July 17, 2026, and lasting for one initial week, Kevin Kruse fired off
|
|
11
|
+
an astonishing array of over six hundred ninety eight BlueSky replies to [an initial post of
|
|
12
|
+
his][postone] featuring a certain government figure. On August 12, 2026, a second wave started [with
|
|
13
|
+
this post][posttwo] aiming at another government figure. (There were also two stray posts from the
|
|
14
|
+
interim period.) This was followed on August 17, 2026, with another series starting with [this
|
|
15
|
+
post][postthree]. A fourth wave started on August 27, 2026, with [this post][postfour]. A fifth
|
|
16
|
+
wave started on August 30, 2026, with [this post][postfive]. A sixth wave started on September 4,
|
|
17
|
+
2026 with [this post][postsix]. The total now stands at one thousand three hundred ninety four
|
|
18
|
+
posts.
|
|
19
|
+
|
|
20
|
+
All posts start with "My man ..." and make for excellent input to a `fortunes`-like package. So this
|
|
21
|
+
small package obliges and offers a random draw each time its `myman()` function is called. This
|
|
22
|
+
Python package benefits from the corresponding [R package][rpkg] and uses the csv file created and
|
|
23
|
+
packaged there.
|
|
24
|
+
|
|
25
|
+
The Python package is equivalent to the R in all aspects but one: each call will only return one
|
|
26
|
+
post.
|
|
27
|
+
|
|
28
|
+
### Example
|
|
29
|
+
|
|
30
|
+
The simple file `main.py` illustrates three calling examples with (numeric) index, target, or
|
|
31
|
+
topical index.
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
$ ./main.py
|
|
35
|
+
My man looks like Pat Boone being tased.
|
|
36
|
+
-- about Stephen Miller on 2026-07-16
|
|
37
|
+
|
|
38
|
+
My man looks like a superhero whose power is Bryl Cream.
|
|
39
|
+
-- about Scott Bessent on 2026-08-31
|
|
40
|
+
|
|
41
|
+
My man looks like he's asked the maitre'd to remove a party of \
|
|
42
|
+
four he finds visually unpleasant.
|
|
43
|
+
-- about Scott Bessent on 2026-08-30
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Author
|
|
48
|
+
|
|
49
|
+
Dirk Eddelbuettel
|
|
50
|
+
|
|
51
|
+
### License
|
|
52
|
+
|
|
53
|
+
GPL (>= 2)
|
|
54
|
+
|
|
55
|
+
[postone]: https://bsky.app/profile/did:plc:cnpe7qvcyjrhm6w7w7e4atur/post/3mqum4mxsuk2g
|
|
56
|
+
[posttwo]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3mstvbjpagca2
|
|
57
|
+
[postthree]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3mtcpiw7gi22j
|
|
58
|
+
[postfour]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3mu3pugs2yk2f
|
|
59
|
+
[postfive]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3mudbzy5ksk25
|
|
60
|
+
[postsix]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3muparqtdkk2w
|
|
61
|
+
|
|
62
|
+
[rpkg]: https://github.com/eddelbuettel/myman
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "myman"
|
|
7
|
+
version = "0.0.6"
|
|
8
|
+
description = "Draw from Sequence of 'My Man' Posts by Kevin Kruse"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Dirk Eddelbuettel", email = "edd@debian.org" }
|
|
11
|
+
]
|
|
12
|
+
requires-python = ">= 3.8"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
dependencies = []
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.packages.find]
|
|
17
|
+
where = ["src"]
|
|
18
|
+
|
|
19
|
+
#[dependency-groups]
|
|
20
|
+
#dev = [
|
|
21
|
+
# "pytest>=8.3.5",
|
|
22
|
+
#]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
test = ["pytest>=7.0"]
|
myman-0.0.6/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import os
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import List, Dict, Optional
|
|
5
|
+
from importlib import resources
|
|
6
|
+
|
|
7
|
+
def load_data(verbose: Option[bool] = False) -> List[Dict]:
|
|
8
|
+
"""
|
|
9
|
+
Loads the raw 'myman.csv' data using Python's built-in csv module.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
A list of dictionaries, where each dictionary represents a row.
|
|
13
|
+
"""
|
|
14
|
+
records: List[Dict] = []
|
|
15
|
+
full_path = resources.files("myman").joinpath("data/myman.csv")
|
|
16
|
+
if verbose:
|
|
17
|
+
print(f"Loading data from: {os.path.abspath(full_path)}")
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
with open(full_path, mode='r', newline='', encoding='utf-8') as file:
|
|
21
|
+
# Use DictReader to automatically handle headers
|
|
22
|
+
reader = csv.DictReader(file)
|
|
23
|
+
|
|
24
|
+
for row in reader:
|
|
25
|
+
# Structure the record for consistency
|
|
26
|
+
record = {
|
|
27
|
+
'quote': row.get('post', '').strip(),
|
|
28
|
+
'target': row.get('man', '').strip(),
|
|
29
|
+
#'date_str': row.get('created', '').strip()
|
|
30
|
+
# Date handling: Keep date as string initially for consistency,
|
|
31
|
+
# but attempt to normalize the format if possible.
|
|
32
|
+
'date_obj': row.get('created', '').strip()
|
|
33
|
+
}
|
|
34
|
+
records.append(record)
|
|
35
|
+
|
|
36
|
+
if verbose:
|
|
37
|
+
print(f"Successfully loaded {len(records)} records.")
|
|
38
|
+
return records
|
|
39
|
+
|
|
40
|
+
except FileNotFoundError:
|
|
41
|
+
print(f"Error: The data file '{csv_file_path}' was not found.")
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
# Example usage when running this script directly
|
|
46
|
+
data = load_data()
|
|
47
|
+
if data:
|
|
48
|
+
print("Data successfully loaded (first record sample):")
|
|
49
|
+
print(data[0])
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import re
|
|
3
|
+
from typing import List, Dict, Optional
|
|
4
|
+
# Import the loading function from the local module
|
|
5
|
+
from .load_data import load_data
|
|
6
|
+
|
|
7
|
+
# class MyMan:
|
|
8
|
+
# def __init__(self, quote, target, date):
|
|
9
|
+
# self.quote = quote
|
|
10
|
+
# self.target = target
|
|
11
|
+
# self.date = date
|
|
12
|
+
|
|
13
|
+
# # Overriding the __str__ method
|
|
14
|
+
# def __str__(self):
|
|
15
|
+
# return f"'{self.quote}' -- directed at {self.target} on {self.date}"
|
|
16
|
+
|
|
17
|
+
def is_string_an_int(arg):
|
|
18
|
+
try:
|
|
19
|
+
int(arg)
|
|
20
|
+
return True
|
|
21
|
+
except ValueError:
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
def format_myman_output(record: Dict) -> str:
|
|
25
|
+
"""
|
|
26
|
+
Formats the sampled record dictionary into the final display string format.
|
|
27
|
+
Format: My man [quote].\t -- about [target] on [date]
|
|
28
|
+
"""
|
|
29
|
+
quote = record['quote']
|
|
30
|
+
target = record['target']
|
|
31
|
+
date_obj = record['date_obj']
|
|
32
|
+
|
|
33
|
+
# Date normalization for clean output
|
|
34
|
+
date_output = date_obj
|
|
35
|
+
if isinstance(date_obj, str):
|
|
36
|
+
# Clean up the date string for display consistency
|
|
37
|
+
date_output = date_obj.split(' ')[0]
|
|
38
|
+
|
|
39
|
+
return f"{quote}.\n\t -- about {target} on {date_output}\n"
|
|
40
|
+
|
|
41
|
+
def myman(ind: Optional[str] = None,
|
|
42
|
+
target: Optional[str] = None,
|
|
43
|
+
verbose: Option[bool] = False) -> str:
|
|
44
|
+
"""
|
|
45
|
+
Randomly samples a single post (skeet) from the loaded dataset,
|
|
46
|
+
optionally filtering by a target keyword.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
int: Optional index, or regex pattern string to match 'post' field
|
|
50
|
+
target: Optional regex pattern string to filter records by the 'target' field.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
A dictionary representing the sampled record, or None if no records are found.
|
|
54
|
+
"""
|
|
55
|
+
# 1. Load all data first
|
|
56
|
+
data = load_data(verbose=verbose)
|
|
57
|
+
if not data:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
records_to_sample = data
|
|
61
|
+
|
|
62
|
+
# 2. Filter if a target is specified
|
|
63
|
+
if target:
|
|
64
|
+
if (verbose):
|
|
65
|
+
print(f"Filtering records by target regex: {target}")
|
|
66
|
+
filtered_records: List[Dict] = []
|
|
67
|
+
for record in data:
|
|
68
|
+
# Use regex search to check if the target keyword exists
|
|
69
|
+
if record['target'] and record['target'].strip():
|
|
70
|
+
try:
|
|
71
|
+
# Use re.search for robust regex matching
|
|
72
|
+
if re.search(target, record['target'], re.IGNORECASE):
|
|
73
|
+
filtered_records.append(record)
|
|
74
|
+
except re.error as e:
|
|
75
|
+
print(f"Error in regex pattern '{target}': {e}")
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
if not filtered_records:
|
|
79
|
+
if (verbose):
|
|
80
|
+
print("No records found matching the target.")
|
|
81
|
+
return ""
|
|
82
|
+
|
|
83
|
+
records_to_sample = filtered_records
|
|
84
|
+
|
|
85
|
+
# 3.1 Index by position or match
|
|
86
|
+
if ind:
|
|
87
|
+
if (verbose):
|
|
88
|
+
print(f"Indexing records by '{ind}'")
|
|
89
|
+
matched_records: List[Dict] = []
|
|
90
|
+
|
|
91
|
+
if is_string_an_int(ind):
|
|
92
|
+
i = int(ind) - 1
|
|
93
|
+
matched_records = records_to_sample[i]
|
|
94
|
+
else:
|
|
95
|
+
for record in records_to_sample:
|
|
96
|
+
# Use regex search to check if the 'ind' pattern matches
|
|
97
|
+
if record['quote'] and record['quote'].strip():
|
|
98
|
+
try:
|
|
99
|
+
# Use re.search for robust regex matching
|
|
100
|
+
if re.search(ind, record['quote'], re.IGNORECASE):
|
|
101
|
+
matched_records.append(record)
|
|
102
|
+
except re.error as e:
|
|
103
|
+
print(f"Error in regex pattern '{ind}': {e}")
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
if not matched_records:
|
|
107
|
+
if (verbose):
|
|
108
|
+
print("No records found matching the pattern.")
|
|
109
|
+
return ""
|
|
110
|
+
else:
|
|
111
|
+
# sample down to one
|
|
112
|
+
matched_records = random.choice(matched_records)
|
|
113
|
+
|
|
114
|
+
records_to_sample = matched_records
|
|
115
|
+
|
|
116
|
+
# 3.2 Or sample the record
|
|
117
|
+
else:
|
|
118
|
+
records_to_sample = random.choice(records_to_sample)
|
|
119
|
+
if (verbose):
|
|
120
|
+
print("Successfully sampled a record.")
|
|
121
|
+
|
|
122
|
+
if not records_to_sample:
|
|
123
|
+
if (verbose):
|
|
124
|
+
print("No records found matching the criteria.")
|
|
125
|
+
return ""
|
|
126
|
+
|
|
127
|
+
return format_myman_output(records_to_sample)
|
|
128
|
+
|
|
129
|
+
# Example usage when running this script directly
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
# Test 1: Sample a random record
|
|
132
|
+
print("\n--- Testing random sample (no target) ---")
|
|
133
|
+
sample_no_target = myman()
|
|
134
|
+
if sample_no_target:
|
|
135
|
+
print("Sampled Record:", sample_no_target)
|
|
136
|
+
|
|
137
|
+
# Test 2: Sample by a specific target (e.g., 'crypto')
|
|
138
|
+
print("\n--- Testing targeted sample (target=crypto) ---")
|
|
139
|
+
sample_target = myman(target="crypto")
|
|
140
|
+
if sample_target:
|
|
141
|
+
print("Sampled Record:", sample_target)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from typing import Optional, Dict
|
|
2
|
+
import random
|
|
3
|
+
import re
|
|
4
|
+
from .myman import myman
|
|
5
|
+
|
|
6
|
+
def format_myman_output(record: Dict) -> str:
|
|
7
|
+
"""
|
|
8
|
+
Formats the sampled record dictionary into the final display string format.
|
|
9
|
+
Format: My man [quote].\t -- about [target] on [date]
|
|
10
|
+
"""
|
|
11
|
+
quote = record['quote']
|
|
12
|
+
target = record['target']
|
|
13
|
+
date_obj = record['date_obj']
|
|
14
|
+
|
|
15
|
+
# Date normalization for clean output
|
|
16
|
+
date_output = date_obj
|
|
17
|
+
if isinstance(date_obj, str):
|
|
18
|
+
# Clean up the date string for display consistency
|
|
19
|
+
date_output = date_obj.split(' ')[0]
|
|
20
|
+
|
|
21
|
+
return f"My man {quote}.\t -- about {target} on {date_output}"
|
|
22
|
+
|
|
23
|
+
def run_myman(ind: Optional[str] = None, target: Optional[str] = None) -> str:
|
|
24
|
+
"""
|
|
25
|
+
High-level function to execute the full cycle: sample data -> format output.
|
|
26
|
+
"""
|
|
27
|
+
# Get the sampled record
|
|
28
|
+
from .myman import myman # Local import for circular dependency protection
|
|
29
|
+
sampled_record = myman(ind=ind, target=target)
|
|
30
|
+
|
|
31
|
+
if sampled_record:
|
|
32
|
+
# Format and return the final string
|
|
33
|
+
return format_myman_output(sampled_record)
|
|
34
|
+
else:
|
|
35
|
+
return "Could not generate content: Sample failed or data is unavailable."
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
# Example usage: generate and print a sample
|
|
39
|
+
print("="*50)
|
|
40
|
+
print("--- Running Python 'myman' Generator ---")
|
|
41
|
+
print("="*50)
|
|
42
|
+
|
|
43
|
+
# Test 1: General sample
|
|
44
|
+
final_output = run_myman(target=None)
|
|
45
|
+
print(final_output)
|
|
46
|
+
|
|
47
|
+
# Test 2: Targeted sample (e.g., 'crypto')
|
|
48
|
+
print("\n" + "="*50)
|
|
49
|
+
final_output_crypto = run_myman(target="crypto")
|
|
50
|
+
print(final_output_crypto)
|
|
51
|
+
print("="*50)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: myman
|
|
3
|
+
Version: 0.0.6
|
|
4
|
+
Summary: Draw from Sequence of 'My Man' Posts by Kevin Kruse
|
|
5
|
+
Author-email: Dirk Eddelbuettel <edd@debian.org>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Provides-Extra: test
|
|
9
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## myman: Sequence of posts by Kevin Kruse starting with 'My man ...'
|
|
13
|
+
|
|
14
|
+
[](https://github.com/eddelbuettel/myman-py/actions?query=workflow%3Aci)
|
|
15
|
+
[](https://opensource.org/license/gpl-2.0)
|
|
16
|
+
[](https://github.com/eddelbuettel/myman-py)
|
|
17
|
+
|
|
18
|
+
### Motivation
|
|
19
|
+
|
|
20
|
+
Starting on the afternoon of July 17, 2026, and lasting for one initial week, Kevin Kruse fired off
|
|
21
|
+
an astonishing array of over six hundred ninety eight BlueSky replies to [an initial post of
|
|
22
|
+
his][postone] featuring a certain government figure. On August 12, 2026, a second wave started [with
|
|
23
|
+
this post][posttwo] aiming at another government figure. (There were also two stray posts from the
|
|
24
|
+
interim period.) This was followed on August 17, 2026, with another series starting with [this
|
|
25
|
+
post][postthree]. A fourth wave started on August 27, 2026, with [this post][postfour]. A fifth
|
|
26
|
+
wave started on August 30, 2026, with [this post][postfive]. A sixth wave started on September 4,
|
|
27
|
+
2026 with [this post][postsix]. The total now stands at one thousand three hundred ninety four
|
|
28
|
+
posts.
|
|
29
|
+
|
|
30
|
+
All posts start with "My man ..." and make for excellent input to a `fortunes`-like package. So this
|
|
31
|
+
small package obliges and offers a random draw each time its `myman()` function is called. This
|
|
32
|
+
Python package benefits from the corresponding [R package][rpkg] and uses the csv file created and
|
|
33
|
+
packaged there.
|
|
34
|
+
|
|
35
|
+
The Python package is equivalent to the R in all aspects but one: each call will only return one
|
|
36
|
+
post.
|
|
37
|
+
|
|
38
|
+
### Example
|
|
39
|
+
|
|
40
|
+
The simple file `main.py` illustrates three calling examples with (numeric) index, target, or
|
|
41
|
+
topical index.
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
$ ./main.py
|
|
45
|
+
My man looks like Pat Boone being tased.
|
|
46
|
+
-- about Stephen Miller on 2026-07-16
|
|
47
|
+
|
|
48
|
+
My man looks like a superhero whose power is Bryl Cream.
|
|
49
|
+
-- about Scott Bessent on 2026-08-31
|
|
50
|
+
|
|
51
|
+
My man looks like he's asked the maitre'd to remove a party of \
|
|
52
|
+
four he finds visually unpleasant.
|
|
53
|
+
-- about Scott Bessent on 2026-08-30
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Author
|
|
58
|
+
|
|
59
|
+
Dirk Eddelbuettel
|
|
60
|
+
|
|
61
|
+
### License
|
|
62
|
+
|
|
63
|
+
GPL (>= 2)
|
|
64
|
+
|
|
65
|
+
[postone]: https://bsky.app/profile/did:plc:cnpe7qvcyjrhm6w7w7e4atur/post/3mqum4mxsuk2g
|
|
66
|
+
[posttwo]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3mstvbjpagca2
|
|
67
|
+
[postthree]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3mtcpiw7gi22j
|
|
68
|
+
[postfour]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3mu3pugs2yk2f
|
|
69
|
+
[postfive]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3mudbzy5ksk25
|
|
70
|
+
[postsix]: https://bsky.app/profile/kevinmkruse.bsky.social/post/3muparqtdkk2w
|
|
71
|
+
|
|
72
|
+
[rpkg]: https://github.com/eddelbuettel/myman
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/myman/__init__.py
|
|
4
|
+
src/myman/load_data.py
|
|
5
|
+
src/myman/myman.py
|
|
6
|
+
src/myman/output_formatter.py
|
|
7
|
+
src/myman.egg-info/PKG-INFO
|
|
8
|
+
src/myman.egg-info/SOURCES.txt
|
|
9
|
+
src/myman.egg-info/dependency_links.txt
|
|
10
|
+
src/myman.egg-info/requires.txt
|
|
11
|
+
src/myman.egg-info/top_level.txt
|
|
12
|
+
tests/test_myman.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
myman
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from myman import myman
|
|
5
|
+
|
|
6
|
+
class TestMyManIndex:
|
|
7
|
+
"""Test basic indexing."""
|
|
8
|
+
|
|
9
|
+
def test_index_one(self):
|
|
10
|
+
"""Test that index '1' returns exptected value."""
|
|
11
|
+
s = myman.myman(ind=1)
|
|
12
|
+
assert s == "My man looks like Pat Boone being tased.\n\t -- about Stephen Miller on 2026-07-16\n"
|
|
13
|
+
|
|
14
|
+
def test_zero_on_bad_target(self):
|
|
15
|
+
"""Test that empty string is return on mismatched subset"""
|
|
16
|
+
s = myman.myman(target="abc123")
|
|
17
|
+
assert s == ""
|
|
18
|
+
|
|
19
|
+
def test_zero_on_bad_index(self):
|
|
20
|
+
"""Test that empty string is return on mismatched subset"""
|
|
21
|
+
s = myman.myman(ind="abc123")
|
|
22
|
+
assert s == ""
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
pytest.main([__file__, "-v"])
|