indicomb2 0.1__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.
indicomb2-0.1/PKG-INFO ADDED
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.1
2
+ Name: indicomb2
3
+ Version: 0.1
4
+ Summary: Faster indicomb with mkdocs markdown support
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: pyyaml>=6.0.2
9
+ Requires-Dist: requests>=2.32.3
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=8.3.3; extra == "dev"
12
+ Requires-Dist: ruff>=0.6.8; extra == "dev"
13
+
14
+ Faster indicomb with mkdocs markdown support.
15
+ An alternative to the original [indicomb](https://gitlab.cern.ch/indicomb/indicomb).
16
+
17
+ This is fast enough to run in the CI as your docs get built (say goodby to your cron jobs!), and comes with mkdocs markdown support.
18
+
19
+
20
+ ## Quick Start
21
+
22
+ ```bash
23
+ pip install indicomb2
24
+ ```
25
+
26
+ Set up a config, see the [example](https://gitlab.cern.ch/indicomb/indicomb2/-/blob/main/example.yaml)
27
+
28
+ Run:
29
+
30
+ ```bash
31
+ indicomb2 -c my_config.yaml
32
+ ```
33
+
34
+
35
+ ## More details
36
+
37
+ 1. Set up a CERN docs site: https://how-to.docs.cern.ch/
38
+ 2. Add an environment variable `INDICO_API_TOKEN` with the token from https://indico.cern.ch/user/tokens/
39
+ 3. Setup a config, see the [example](https://gitlab.cern.ch/indicomb/indicomb2/-/blob/main/example.yaml)
40
+ 4. Run `indicomb2 -c my_config.yaml`
@@ -0,0 +1,27 @@
1
+ Faster indicomb with mkdocs markdown support.
2
+ An alternative to the original [indicomb](https://gitlab.cern.ch/indicomb/indicomb).
3
+
4
+ This is fast enough to run in the CI as your docs get built (say goodby to your cron jobs!), and comes with mkdocs markdown support.
5
+
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ pip install indicomb2
11
+ ```
12
+
13
+ Set up a config, see the [example](https://gitlab.cern.ch/indicomb/indicomb2/-/blob/main/example.yaml)
14
+
15
+ Run:
16
+
17
+ ```bash
18
+ indicomb2 -c my_config.yaml
19
+ ```
20
+
21
+
22
+ ## More details
23
+
24
+ 1. Set up a CERN docs site: https://how-to.docs.cern.ch/
25
+ 2. Add an environment variable `INDICO_API_TOKEN` with the token from https://indico.cern.ch/user/tokens/
26
+ 3. Setup a config, see the [example](https://gitlab.cern.ch/indicomb/indicomb2/-/blob/main/example.yaml)
27
+ 4. Run `indicomb2 -c my_config.yaml`
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "indicomb2"
3
+ description = "Faster indicomb with mkdocs markdown support"
4
+ readme = "README.md"
5
+ license = { text = "MIT" }
6
+ dynamic = ["version"]
7
+ requires-python = ">=3.10"
8
+ dependencies = [
9
+ "pyyaml>=6.0.2",
10
+ "requests>=2.32.3",
11
+ ]
12
+
13
+ [project.optional-dependencies]
14
+ dev = [
15
+ "pytest>=8.3.3",
16
+ "ruff>=0.6.8",
17
+ ]
18
+
19
+ [tool.setuptools.dynamic]
20
+ version = { attr = "indicomb2.__version__" }
21
+
22
+ [project.scripts]
23
+ indicomb2 = "indicomb2.indicomb2:main"
24
+
25
+ [tool.ruff]
26
+ src = ["src"]
27
+ target-version = "py310"
28
+ line-length = 100
29
+ preview = true
30
+ lint.select = ["ALL"]
31
+ lint.ignore = ["G", "D", "ANN", "PLW", "FBT", "COM812", "ISC001", "CPY001", "TRY003", "S101", "A003"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = "0.1"
@@ -0,0 +1,27 @@
1
+ import datetime
2
+ from dataclasses import dataclass
3
+
4
+
5
+ @dataclass
6
+ class Contribution:
7
+ title: str
8
+ url: str
9
+ speakers: list[dict] | str
10
+ minutes: str
11
+ start_time: str = None # HH:MM:SS
12
+
13
+ @classmethod
14
+ def from_indico(cls, start_time: str, speakers: list[dict], **kwargs):
15
+ start_time = datetime.datetime.strptime(start_time, "%H:%M:%S").replace(
16
+ tzinfo=datetime.timezone.utc
17
+ )
18
+ speakers = ", ".join(
19
+ [f"{s['first_name']} {s['last_name']}" for s in speakers],
20
+ )
21
+ return cls(speakers=speakers, start_time=start_time, **kwargs)
22
+
23
+ def __str__(self):
24
+ return f" - [{self.title}]({self.url}) - {self.speakers}"
25
+
26
+ def __lt__(self, other):
27
+ return self.start_time < other.start_time
@@ -0,0 +1,52 @@
1
+ from dataclasses import dataclass
2
+
3
+ from indicomb2.contribution import Contribution
4
+ from indicomb2.markdown import Markdown
5
+
6
+
7
+ @dataclass
8
+ class Event:
9
+ url: str
10
+ date: str
11
+ title: str
12
+ contributions: list[dict] | list[Contribution]
13
+
14
+ @classmethod
15
+ def from_indico(cls, url: str, date: str, title: str, contributions: list[dict]):
16
+ contributions = sorted(
17
+ [
18
+ Contribution.from_indico(
19
+ title=c["title"],
20
+ url=c["url"],
21
+ speakers=c["speakers"],
22
+ minutes=c.get("note"),
23
+ start_time=c["startDate"]["time"],
24
+ )
25
+ for c in reversed(contributions)
26
+ ],
27
+ )
28
+ return cls(url, date, title, contributions)
29
+
30
+ @classmethod
31
+ def from_json(cls, url: str, date: str, title: str, contributions: list[Contribution]):
32
+ contributions = [Contribution(**c) for c in contributions]
33
+ return cls(url, date, title, contributions)
34
+
35
+ def __lt__(self, other):
36
+ return self.date > other.date
37
+
38
+ def __str__(self):
39
+ md = Markdown()
40
+ date = md.link(self.date, self.url)
41
+ md += md.header(f"{date} - {self.title}", level=4)
42
+ md += "\n".join([str(c) for c in self.contributions])
43
+ return str(md)
44
+
45
+ def minutes(self):
46
+ md = Markdown()
47
+ date = md.link(self.date, self.url)
48
+ md += md.header(f"{date} - {self.title}", level=4)
49
+ for c in self.contributions:
50
+ if c.minutes:
51
+ md += md.admonition("note", str(c)[3:], str(c.minutes["html"]))
52
+ return str(md)
@@ -0,0 +1,182 @@
1
+ import argparse
2
+ import datetime
3
+ import json
4
+ import logging
5
+ import multiprocessing as mp
6
+ import os
7
+ import urllib
8
+ from dataclasses import asdict, dataclass
9
+ from pathlib import Path
10
+
11
+ import requests
12
+ import yaml
13
+
14
+ from indicomb2.event import Event
15
+ from indicomb2.meetings import Meetings
16
+
17
+ logging.basicConfig(format="%(asctime)s - %(message)s", level=logging.INFO)
18
+
19
+
20
+ class BearerAuth(requests.auth.AuthBase):
21
+ def __init__(self, token: str):
22
+ self.token = token
23
+
24
+ def __call__(self, req):
25
+ req.headers["authorization"] = "Bearer " + self.token
26
+ return req
27
+
28
+
29
+ @dataclass
30
+ class Indico:
31
+ category_id: int
32
+ api_token: str = None
33
+ site: str = "https://indico.cern.ch"
34
+ years: int = 1
35
+
36
+ def __post_init__(self):
37
+ if self.api_token is None:
38
+ self.api_token = os.environ.get("INDICO_API_TOKEN", None)
39
+ if self.api_token is None:
40
+ msg = (
41
+ "Please provide an API token or set INDICO_API_TOKEN or INDICO_API_KEY in your env"
42
+ )
43
+ raise ValueError(msg)
44
+
45
+ self.url = f"{self.site}/category/{self.category_id}/"
46
+ today = datetime.datetime.now(tz=datetime.timezone.utc).date()
47
+ start_date = today - datetime.timedelta(days=365 * self.years)
48
+ self.start_date = start_date.strftime("%Y-%m-%d")
49
+ logging.info(
50
+ f"Feteching meetings for {self.category_id} from {self.start_date}",
51
+ )
52
+ self.events = self.get_category(
53
+ category_id=self.category_id,
54
+ start_date=self.start_date,
55
+ )
56
+
57
+ def request(self, path: str, params: dict):
58
+ items = list(params.items()) if hasattr(params, "items") else list(params)
59
+ items = sorted(items, key=lambda x: x[0].lower())
60
+ url = f"{self.site}{path}?{urllib.parse.urlencode(items)}"
61
+ response = requests.get(url, auth=BearerAuth(self.api_token), timeout=30)
62
+ response.encoding = response.apparent_encoding
63
+ response.raise_for_status()
64
+ return response.json()
65
+
66
+ def get_category(self, category_id: int, start_date: str, end_date: str = "+15d"):
67
+ category_params = {"from": start_date, "to": end_date, "order": "start"}
68
+ category_path = f"/export/categ/{category_id}.json"
69
+ return self.request(category_path, params=category_params)["results"]
70
+
71
+ @staticmethod
72
+ def filter_events(
73
+ results: dict,
74
+ include: list[str] | None = None,
75
+ exclude: list[str] | None = None,
76
+ ):
77
+ if include is None:
78
+ include = []
79
+ if exclude is None:
80
+ exclude = []
81
+ include = [x.lower() for x in include]
82
+ exclude = [x.lower() for x in exclude]
83
+ return [
84
+ r
85
+ for r in results
86
+ if any(x in r["title"].lower() for x in include)
87
+ and not any(x in r["title"].lower() for x in exclude)
88
+ ]
89
+
90
+ def get_contributions(self, event: dict, params: dict):
91
+ event_path = f"/export/event/{event['id']}.json"
92
+ event = self.request(event_path, params=params)["results"]
93
+ assert len(event) == 1
94
+ event = event[0]
95
+ return Event.from_indico(
96
+ url=event["url"],
97
+ date=event["startDate"]["date"],
98
+ title=event["title"],
99
+ contributions=event["contributions"],
100
+ )
101
+
102
+ def get_meetings(
103
+ self,
104
+ include: list[str] | None = None,
105
+ exclude: list[str] | None = None,
106
+ ) -> Meetings:
107
+ if exclude is None:
108
+ exclude = []
109
+ exclude += ["cancelled"]
110
+ events = self.filter_events(self.events, include=include, exclude=exclude)
111
+ params = {"detail": "contributions"}
112
+ results = []
113
+ pool = mp.Pool(processes=mp.cpu_count())
114
+ for event in events:
115
+ kwargs = {"event": event, "params": params}
116
+ pool.apply_async(self.get_contributions, kwds=kwargs, callback=results.append)
117
+ pool.close()
118
+ pool.join()
119
+ logging.info(f"Fetched {len(results)} events")
120
+ return Meetings(sorted(results), self.url, self.start_date)
121
+
122
+
123
+ def main(args=None):
124
+ parser = argparse.ArgumentParser()
125
+ parser.add_argument(
126
+ "-c",
127
+ "--config",
128
+ required=True,
129
+ type=str,
130
+ help="Path to the config file",
131
+ )
132
+ args = parser.parse_args(args)
133
+
134
+ # load config
135
+ with Path(args.config).open() as f:
136
+ config = yaml.safe_load(f)
137
+
138
+ # get sub configs
139
+ summary_config = config["meeting_summaries"]
140
+ topical_contributions_config = config["topical_contributions"]
141
+ minutes_config = config["minutes_summary"]
142
+
143
+ # dump meetings to json
144
+ all_meetings = {}
145
+ for entry in config["scrape"]:
146
+ ind = Indico(category_id=entry["category"], years=entry["years"])
147
+ for name, kwargs in entry["groups"].items():
148
+ logging.info(f"Fetching meetings for {name}")
149
+ meetings = ind.get_meetings(**kwargs)
150
+ all_meetings[name] = meetings
151
+ Path("json").mkdir(exist_ok=True)
152
+ fpath = Path(f"json/{name}.json")
153
+ with fpath.open("w") as f:
154
+ meetings_dict = [asdict(m) for m in meetings.meetings]
155
+ json.dump(meetings_dict, f, indent=4, default=str)
156
+ logging.info(f"Saved to {fpath}")
157
+
158
+ # meeting summaries
159
+ for name, kwargs in summary_config.items():
160
+ logging.info(f"Creating {name} summary")
161
+ meetings = all_meetings[name]
162
+ meetings.meeting_list_md(name=name, **kwargs)
163
+
164
+ # add contribution tables
165
+ logging.info("Adding topical contributions")
166
+ for config in topical_contributions_config:
167
+ cats = config.get("categories", list(all_meetings.keys()))
168
+ meetings = sum((all_meetings[cat] for cat in cats[1:]), all_meetings[cats[0]])
169
+ meetings.add_topic(
170
+ target=config["target"],
171
+ include=config["include"],
172
+ exclude=config.get("exclude"),
173
+ )
174
+
175
+ # add minutes tables
176
+ for name, config in minutes_config.items():
177
+ logging.info(f"Adding minutes to {config['target']}")
178
+ all_meetings[name].minutes_md(name=name, target=config["target"])
179
+
180
+
181
+ if __name__ == "__main__":
182
+ main()
@@ -0,0 +1,116 @@
1
+ from pathlib import Path
2
+
3
+
4
+ class Markdown:
5
+ """Class to write markdown for material for mkdocs."""
6
+
7
+ def __init__(self, md: str = "", hide_toc=False) -> None:
8
+ self.md = md
9
+ if hide_toc:
10
+ self.md += "\n".join(["---", "hide:", " - toc", "---"]) # noqa: FLY002
11
+
12
+ def __add__(self, other):
13
+ if not other.startswith("\n") and self.md:
14
+ other = "\n" + other
15
+ self.md += other
16
+ return self
17
+
18
+ def __radd__(self, other):
19
+ if not self.md.startswith("\n\n") and other:
20
+ self.md = "\n\n" + self.md
21
+ return other + str(self)
22
+
23
+ def __str__(self) -> str:
24
+ return self.md
25
+
26
+ @staticmethod
27
+ def indent(text):
28
+ return "\t" + text.replace("\n", "\n\t")
29
+
30
+ @staticmethod
31
+ def header(text, level):
32
+ return "#" * level + " " + text + "\n"
33
+
34
+ @staticmethod
35
+ def paragraph(text):
36
+ return text + "\n\n"
37
+
38
+ @staticmethod
39
+ def code(text, language="", line_numbers=False):
40
+ ln = ""
41
+ if line_numbers:
42
+ ln += ' linenums="1"'
43
+ return "\n\n```" + language + ln + "\n" + text + "\n```\n\n"
44
+
45
+ @staticmethod
46
+ def link(text, url):
47
+ return "[" + text + "](" + url + ")"
48
+
49
+ @staticmethod
50
+ def image(text, url):
51
+ return "![" + text + "](" + url + ")"
52
+
53
+ @staticmethod
54
+ def table(data: dict[str, list], format_ints=True):
55
+ assert len({len(row) for row in data.values()}) == 1
56
+ text = "| "
57
+ for header in data:
58
+ text += header + " | "
59
+ text += "\n| "
60
+ for header in data:
61
+ text += "-" * len(header) + " | "
62
+ text += "\n"
63
+
64
+ for row_idx in range(len(next(iter(data.values())))):
65
+ text += "| "
66
+ for col_idx in range(len(data.keys())):
67
+ value = data[list(data.keys())[col_idx]][row_idx]
68
+ if (format_ints and (isinstance(value, str) and value.isdigit())) or isinstance(
69
+ value, int
70
+ ):
71
+ value = f"{int(value):,}"
72
+ text += str(value) + " | "
73
+ text += "\n"
74
+
75
+ text += "\n\n"
76
+ return text
77
+
78
+ @staticmethod
79
+ def list(items):
80
+ text = ""
81
+ for item in items:
82
+ text += "- " + item + "\n"
83
+ text += "\n\n"
84
+ return text
85
+
86
+ def admonition(self, style, header, body=None, expanded=False):
87
+ if body is None:
88
+ expanded = False
89
+ md = "\n"
90
+ md += "!!!" if body is None else "???"
91
+ if expanded:
92
+ md += "+"
93
+ md += " "
94
+ md += style + ' "' + header + '"\n\n'
95
+ if body:
96
+ md += self.indent(body) + "\n\n"
97
+ return md
98
+
99
+ def tabs(self, tabs: dict[str, str]):
100
+ text = "\n"
101
+ for header, body in tabs.items():
102
+ text += "=== " + f'"{header}"' + " \n\n"
103
+ text += self.indent(body) + "\n\n"
104
+ return text
105
+
106
+ @staticmethod
107
+ def bold_text(text):
108
+ return f"**{text}**"
109
+
110
+ @staticmethod
111
+ def italic_text(text):
112
+ return f"*{text}*"
113
+
114
+ def to_file(self, fname):
115
+ with Path(fname).open("w") as f:
116
+ f.write(self.md)
@@ -0,0 +1,91 @@
1
+ import datetime
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+
5
+ from indicomb2.event import Event
6
+ from indicomb2.markdown import Markdown
7
+
8
+
9
+ @dataclass
10
+ class Meetings:
11
+ meetings: list[Event]
12
+ url: str
13
+ start_date: str
14
+
15
+ def __post_init__(self):
16
+ self.meetings = sorted(self.meetings)
17
+
18
+ def __add__(self, other):
19
+ return Meetings(self.meetings + other.meetings, self.url, self.start_date)
20
+
21
+ def meeting_list_md(self, name: str, when: str, target: Path | str):
22
+ target = Path(target)
23
+ target.parent.mkdir(parents=True, exist_ok=True)
24
+ md = Markdown()
25
+ md += md.header(f"{name.capitalize()} Meetings", level=1)
26
+ name = md.link(name, self.url)
27
+ md += f"The {name} meetings take place every {when} CERN time."
28
+ md += "\n\nYou can find a meeting overview below.\n\n"
29
+ md += md.header("Meeting Overview", level=2)
30
+ last_updated = datetime.datetime.now(tz=datetime.timezone.utc).strftime("%Y-%m-%d %H:%M")
31
+ md += f"*last updated: {last_updated}*\n"
32
+ md += "\n\n".join([str(m) for m in self.meetings])
33
+ md += "\n\n"
34
+ md += (
35
+ f"*date cutoff: {self.start_date}, for older meetings please check "
36
+ f"[indico]({self.url})*\n"
37
+ )
38
+ with target.open("w") as f:
39
+ f.write(str(md))
40
+
41
+ def minutes_md(self, name: str, target: Path):
42
+ target = Path(target)
43
+ target.parent.mkdir(parents=True, exist_ok=True)
44
+ md = Markdown()
45
+ md += md.header(f"{name.capitalize()} Meetings Minutes", level=1)
46
+ last_updated = datetime.datetime.now(tz=datetime.timezone.utc).strftime("%Y-%m-%d %H:%M")
47
+ md += f"*last updated: {last_updated}*\n"
48
+ md += "\n\n".join([m.minutes() for m in self.meetings])
49
+ md += "\n\n"
50
+ md += (
51
+ f"*date cutoff: {self.start_date}, for older meetings please check "
52
+ f"[indico]({self.url})*\n"
53
+ )
54
+ md += md.link("indico", self.url)
55
+ with target.open("w") as f:
56
+ f.write(str(md))
57
+
58
+ def add_topic(
59
+ self,
60
+ target: Path,
61
+ include: list[str] | None = None,
62
+ exclude: list[str] | None = None,
63
+ ):
64
+ if include is None:
65
+ include = []
66
+ if exclude is None:
67
+ exclude = []
68
+ data = {"Date": [], "Title": [], "Speakers": []}
69
+ for e in self.meetings:
70
+ for c in e.contributions:
71
+ if any(x.lower() in c.title.lower() for x in include) and not any(
72
+ x.lower() in c.title.lower() for x in exclude
73
+ ):
74
+ data["Date"].append(f"[{e.date}]({c.url})")
75
+ data["Title"].append(c.title)
76
+ data["Speakers"].append(c.speakers)
77
+
78
+ md = Markdown()
79
+ md += "\n"
80
+ md += md.header("Meeting Contributions", level=2)
81
+ md += md.table(data)
82
+ md += "\n\n"
83
+ md += f"*search terms: {include}*\n"
84
+ md += (
85
+ f"*date cutoff: {self.start_date}, for older meetings please check "
86
+ f"[indico]({self.url})*\n"
87
+ )
88
+ target = Path(target)
89
+ target.parent.mkdir(parents=True, exist_ok=True)
90
+ with target.open("w") as f:
91
+ f.write(str(md))
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.1
2
+ Name: indicomb2
3
+ Version: 0.1
4
+ Summary: Faster indicomb with mkdocs markdown support
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: pyyaml>=6.0.2
9
+ Requires-Dist: requests>=2.32.3
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=8.3.3; extra == "dev"
12
+ Requires-Dist: ruff>=0.6.8; extra == "dev"
13
+
14
+ Faster indicomb with mkdocs markdown support.
15
+ An alternative to the original [indicomb](https://gitlab.cern.ch/indicomb/indicomb).
16
+
17
+ This is fast enough to run in the CI as your docs get built (say goodby to your cron jobs!), and comes with mkdocs markdown support.
18
+
19
+
20
+ ## Quick Start
21
+
22
+ ```bash
23
+ pip install indicomb2
24
+ ```
25
+
26
+ Set up a config, see the [example](https://gitlab.cern.ch/indicomb/indicomb2/-/blob/main/example.yaml)
27
+
28
+ Run:
29
+
30
+ ```bash
31
+ indicomb2 -c my_config.yaml
32
+ ```
33
+
34
+
35
+ ## More details
36
+
37
+ 1. Set up a CERN docs site: https://how-to.docs.cern.ch/
38
+ 2. Add an environment variable `INDICO_API_TOKEN` with the token from https://indico.cern.ch/user/tokens/
39
+ 3. Setup a config, see the [example](https://gitlab.cern.ch/indicomb/indicomb2/-/blob/main/example.yaml)
40
+ 4. Run `indicomb2 -c my_config.yaml`
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/indicomb2/__init__.py
4
+ src/indicomb2/contribution.py
5
+ src/indicomb2/event.py
6
+ src/indicomb2/indicomb2.py
7
+ src/indicomb2/markdown.py
8
+ src/indicomb2/meetings.py
9
+ src/indicomb2.egg-info/PKG-INFO
10
+ src/indicomb2.egg-info/SOURCES.txt
11
+ src/indicomb2.egg-info/dependency_links.txt
12
+ src/indicomb2.egg-info/entry_points.txt
13
+ src/indicomb2.egg-info/requires.txt
14
+ src/indicomb2.egg-info/top_level.txt
15
+ tests/test_indicomb.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ indicomb2 = indicomb2.indicomb2:main
@@ -0,0 +1,6 @@
1
+ pyyaml>=6.0.2
2
+ requests>=2.32.3
3
+
4
+ [dev]
5
+ pytest>=8.3.3
6
+ ruff>=0.6.8
@@ -0,0 +1 @@
1
+ indicomb2
@@ -0,0 +1,13 @@
1
+ from pathlib import Path
2
+
3
+ from indicomb2.indicomb2 import main
4
+
5
+
6
+ def test_indicomb():
7
+ example_config = Path(__file__).parents[1] / "example.yaml"
8
+ args = ["--config", str(example_config)]
9
+ main(args)
10
+
11
+
12
+ if __name__ == "__main__":
13
+ test_indicomb()