whatnext 0.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.
whatnext-0.0.1/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mark Norman Francis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
whatnext-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mark Norman Francis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: whatnext
3
+ Version: 0.0.1
4
+ Summary: A lister of tasks found in Markdown files
5
+ Author: Mark Norman Francis
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/norm/whatnext
8
+ Project-URL: Repository, https://github.com/norm/whatnext
9
+ Project-URL: Documentation, https://github.com/norm/whatnext
10
+ Keywords: cli,markdown,tasks,todo
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENCE
18
+ License-File: LICENSE
19
+ Requires-Dist: toml
20
+ Provides-Extra: test
21
+ Requires-Dist: flake8; extra == "test"
22
+ Requires-Dist: pytest; extra == "test"
23
+ Dynamic: license-file
24
+
25
+ # What next?
26
+
27
+ Document your tasks in Markdown files, using ["standard"][sn] notation:
28
+
29
+ ```markdown
30
+ - [ ] empty, this task is outstanding
31
+ - [X] crossed, this task has been completed
32
+ ```
33
+
34
+ Then install `whatnext`:
35
+
36
+ ```bash
37
+ pip install whatnext
38
+ ```
39
+
40
+ Now run it and it'll tell you what's next:
41
+
42
+ ```bash
43
+ (computer)% whatnext
44
+ README.md:
45
+ # What next?
46
+ - [ ] empty, this task is outstanding
47
+ ```
48
+
49
+ More detail to be found:
50
+
51
+ - [The basics of task formatting](docs/basics.md)
52
+ - [whatnext usage and arguments](docs/usage.md)
53
+ - [The `.whatnext` file](docs/dotwhatnext.md)
54
+
55
+
56
+ ## The reason
57
+
58
+ I like to keep tasks in Markdown files. That way they can be interspersed
59
+ within instructions, serving as reminders, FIXMEs, and other todos.
60
+
61
+
62
+ [sn]: https://blog.github.com/2013-01-09-task-lists-in-gfm-issues-pulls-comments/
@@ -0,0 +1,38 @@
1
+ # What next?
2
+
3
+ Document your tasks in Markdown files, using ["standard"][sn] notation:
4
+
5
+ ```markdown
6
+ - [ ] empty, this task is outstanding
7
+ - [X] crossed, this task has been completed
8
+ ```
9
+
10
+ Then install `whatnext`:
11
+
12
+ ```bash
13
+ pip install whatnext
14
+ ```
15
+
16
+ Now run it and it'll tell you what's next:
17
+
18
+ ```bash
19
+ (computer)% whatnext
20
+ README.md:
21
+ # What next?
22
+ - [ ] empty, this task is outstanding
23
+ ```
24
+
25
+ More detail to be found:
26
+
27
+ - [The basics of task formatting](docs/basics.md)
28
+ - [whatnext usage and arguments](docs/usage.md)
29
+ - [The `.whatnext` file](docs/dotwhatnext.md)
30
+
31
+
32
+ ## The reason
33
+
34
+ I like to keep tasks in Markdown files. That way they can be interspersed
35
+ within instructions, serving as reminders, FIXMEs, and other todos.
36
+
37
+
38
+ [sn]: https://blog.github.com/2013-01-09-task-lists-in-gfm-issues-pulls-comments/
@@ -0,0 +1,44 @@
1
+ [project]
2
+ name = "whatnext"
3
+ version = "0.0.1"
4
+ description = "A lister of tasks found in Markdown files"
5
+ readme = "README.md"
6
+ authors = [{ name = "Mark Norman Francis"}]
7
+ license = "MIT"
8
+ requires-python = ">=3.10"
9
+ keywords = [
10
+ "cli",
11
+ "markdown",
12
+ "tasks",
13
+ "todo",
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ ]
21
+ dependencies = [
22
+ "toml",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/norm/whatnext"
27
+ Repository = "https://github.com/norm/whatnext"
28
+ Documentation = "https://github.com/norm/whatnext"
29
+
30
+ [project.optional-dependencies]
31
+ test = [
32
+ "flake8",
33
+ "pytest",
34
+ ]
35
+
36
+ [project.scripts]
37
+ whatnext = "whatnext.cli:main"
38
+
39
+ [tool.setuptools]
40
+ packages = ["whatnext"]
41
+
42
+ [build-system]
43
+ requires = ["setuptools>=61.0"]
44
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,44 @@
1
+ from whatnext.models import Task, State
2
+
3
+
4
+ class TestTask:
5
+ task = Task(
6
+ "# Indicating the state of a task / Multiline tasks and indentation",
7
+
8
+ "- [ ] Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
9
+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
10
+
11
+ State.INCOMPLETE,
12
+ )
13
+
14
+ def test_wraps_at_40_chars(self):
15
+ assert self.task.wrapped_text(width=40) == [
16
+ " - [ ] Lorem ipsum dolor sit amet,",
17
+ " consectetur adipisicing elit,",
18
+ " sed do eiusmod tempor",
19
+ " incididunt ut labore et dolore",
20
+ " magna aliqua.",
21
+ ]
22
+ assert self.task.wrapped_heading(width=40) == [
23
+ " # Indicating the state of a task /",
24
+ " Multiline tasks and indentation",
25
+ ]
26
+
27
+ def test_no_wrap_at_120_chars(self):
28
+ assert self.task.wrapped_text(width=120) == [
29
+ " - [ ] Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
30
+ "sed do eiusmod tempor incididunt ut labore et dolore",
31
+ " magna aliqua.",
32
+ ]
33
+ assert self.task.wrapped_heading(width=120) == [
34
+ " # Indicating the state of a task / Multiline tasks and indentation"
35
+ ]
36
+
37
+ def test_default_width_80_chars(self):
38
+ assert self.task.wrapped_text() == [
39
+ " - [ ] Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do", # noqa: E501
40
+ " eiusmod tempor incididunt ut labore et dolore magna aliqua.",
41
+ ]
42
+ assert self.task.wrapped_heading() == [
43
+ " # Indicating the state of a task / Multiline tasks and indentation"
44
+ ]
File without changes
@@ -0,0 +1,193 @@
1
+ import argparse
2
+ import fnmatch
3
+ import os
4
+ import shutil
5
+ import toml
6
+
7
+ from whatnext.models import MarkdownFile
8
+
9
+
10
+ def get_terminal_width():
11
+ columns_env = os.environ.get("COLUMNS")
12
+ if columns_env:
13
+ width = int(columns_env)
14
+ else:
15
+ width = shutil.get_terminal_size().columns
16
+ if width < 40:
17
+ width = 80
18
+ return width
19
+
20
+
21
+ def load_config(config_path=None, directory="."):
22
+ if config_path is None:
23
+ config_path = os.path.join(directory, ".whatnext")
24
+ if os.path.exists(config_path):
25
+ with open(config_path) as handle:
26
+ return toml.load(handle)
27
+ return {}
28
+
29
+
30
+ def is_ignored(filepath, ignore_patterns):
31
+ for pattern in ignore_patterns:
32
+ if fnmatch.fnmatch(filepath, pattern):
33
+ return True
34
+ return False
35
+
36
+
37
+ def find_markdown_files(directory=".", ignore_patterns=None, include_all=False):
38
+ if ignore_patterns is None:
39
+ ignore_patterns = []
40
+ markdown_files = []
41
+ for root, dirs, files in os.walk(directory):
42
+ for filename in files:
43
+ if filename.endswith(".md"):
44
+ filepath = os.path.join(root, filename)
45
+ relative_path = os.path.relpath(filepath, directory)
46
+ if is_ignored(relative_path, ignore_patterns):
47
+ continue
48
+ md_file = MarkdownFile(filepath, directory)
49
+ tasks = md_file.tasks if include_all else md_file.incomplete
50
+ if tasks:
51
+ markdown_files.append(md_file)
52
+
53
+ # files are examined depth-last as a lightweight prioritisation
54
+ return sorted(
55
+ markdown_files,
56
+ key=lambda file: (
57
+ file.display_path.count(os.sep),
58
+ file.display_path,
59
+ )
60
+ )
61
+
62
+
63
+ def progress_bar(complete, total, width):
64
+ filled = round(width * complete / total) if total else 0
65
+ return "█" * filled + "░" * (width - filled)
66
+
67
+
68
+ def format_summary(markdown_files, width):
69
+ widest = max(len(mf.tasks) for mf in markdown_files)
70
+ count_width = len(f"{widest}/{widest}")
71
+ gap = " "
72
+ bar_width = (
73
+ width
74
+ - count_width
75
+ - max(len(mf.display_path) for mf in markdown_files)
76
+ - len(gap) * 3
77
+ )
78
+
79
+ lines = []
80
+ for md_file in markdown_files:
81
+ total = len(md_file.tasks)
82
+ complete = len(md_file.complete)
83
+ bar = progress_bar(
84
+ complete,
85
+ total,
86
+ round(bar_width * total / widest)
87
+ )
88
+
89
+ lines.append(
90
+ f"{bar.ljust(bar_width)}{gap}"
91
+ f"{f'{complete}/{total}'.rjust(count_width)}{gap}"
92
+ f"{md_file.display_path}"
93
+ )
94
+
95
+ return "\n".join(lines)
96
+
97
+
98
+ def format_tasks(markdown_files, width, include_all):
99
+ lines = []
100
+ for md_file in markdown_files:
101
+ tasks = md_file.tasks if include_all else md_file.incomplete
102
+ lines.append(f"{md_file.display_path}:")
103
+ current_heading = None
104
+ for task in tasks:
105
+ if task.heading and task.heading != current_heading:
106
+ lines.extend(task.wrapped_heading(width))
107
+ current_heading = task.heading
108
+ lines.extend(task.wrapped_text(width))
109
+ return "\n".join(lines)
110
+
111
+
112
+ class CapitalisedHelpFormatter(argparse.RawDescriptionHelpFormatter):
113
+ def add_usage(self, usage, actions, groups, prefix=None):
114
+ if prefix is None:
115
+ prefix = "Usage: "
116
+ super().add_usage(usage, actions, groups, prefix)
117
+
118
+ def start_section(self, heading):
119
+ super().start_section(heading.capitalize() if heading else heading)
120
+
121
+
122
+ class ShortHelpAction(argparse.Action):
123
+ def __init__(self, option_strings, dest, **kwargs):
124
+ super().__init__(option_strings, dest, nargs=0, **kwargs)
125
+
126
+ def __call__(self, parser, namespace, values, option_string=None):
127
+ print(parser.format_usage().rstrip())
128
+ parser.exit()
129
+
130
+
131
+ def main():
132
+ parser = argparse.ArgumentParser(
133
+ description="List tasks found in Markdown files",
134
+ epilog="""\
135
+ Task States:
136
+ - [ ] Not started (shown by default)
137
+ - [X] Done (hidden by default, use --all)
138
+ """,
139
+ add_help=False,
140
+ formatter_class=CapitalisedHelpFormatter,
141
+ )
142
+ parser.add_argument(
143
+ "-h",
144
+ action=ShortHelpAction,
145
+ help="Show the usage reminder and exit",
146
+ )
147
+ parser.add_argument(
148
+ "--help",
149
+ action="help",
150
+ help="Show this help message and exit",
151
+ )
152
+ parser.add_argument(
153
+ "--dir",
154
+ default=".",
155
+ help="Directory to search (default: current directory)",
156
+ )
157
+ parser.add_argument(
158
+ "-s", "--summary",
159
+ action="store_true",
160
+ help="Show summary of task counts per file",
161
+ )
162
+ parser.add_argument(
163
+ "-a", "--all",
164
+ action="store_true",
165
+ help="Include all tasks and files, not just incomplete",
166
+ )
167
+ parser.add_argument(
168
+ "--config",
169
+ help="Path to config file (default: .whatnext in search directory)",
170
+ )
171
+ parser.add_argument(
172
+ "--ignore",
173
+ action="append",
174
+ default=[],
175
+ metavar="PATTERN",
176
+ help="Ignore files matching pattern (can be specified multiple times)",
177
+ )
178
+ args = parser.parse_args()
179
+
180
+ config_path = args.config or os.environ.get("WHATNEXT_CONFIG")
181
+ config = load_config(config_path, args.dir)
182
+ ignore_patterns = config.get("ignore", []) + args.ignore
183
+ markdown_files = find_markdown_files(args.dir, ignore_patterns, args.all)
184
+
185
+ if not markdown_files:
186
+ return
187
+
188
+ width = get_terminal_width()
189
+
190
+ if args.summary:
191
+ print(format_summary(markdown_files, width))
192
+ else:
193
+ print(format_tasks(markdown_files, width, args.all))
@@ -0,0 +1,150 @@
1
+ from enum import Enum
2
+ import os
3
+ import re
4
+ import textwrap
5
+
6
+
7
+ class State(Enum):
8
+ def __new__(cls, value, markers):
9
+ obj = object.__new__(cls)
10
+ obj._value_ = value
11
+ obj.markers = markers
12
+ return obj
13
+
14
+ INCOMPLETE = ("incomplete", [" "])
15
+ COMPLETE = ("complete", ["X", "x"])
16
+
17
+ @classmethod
18
+ def all_markers(cls):
19
+ markers = []
20
+ for state in cls:
21
+ markers.extend(state.markers)
22
+ return markers
23
+
24
+ @classmethod
25
+ def from_marker(cls, marker):
26
+ for state in cls:
27
+ if marker in state.markers:
28
+ return state
29
+ raise ValueError(f"Unknown marker: {marker}")
30
+
31
+
32
+ class Task:
33
+ def __init__(self, heading, text, state):
34
+ self.heading = heading
35
+ self.text = text
36
+ self.state = state
37
+
38
+ def wrapped_text(self, width=80, indent=" "):
39
+ text = " ".join(self.text.split())
40
+ if width is None or len(indent + text) <= width:
41
+ return [indent + text]
42
+ return textwrap.wrap(
43
+ text,
44
+ width=width,
45
+ initial_indent=indent,
46
+ subsequent_indent=indent + " ",
47
+ )
48
+
49
+ def wrapped_heading(self, width=80, indent=" "):
50
+ if not self.heading:
51
+ return []
52
+ if width is None or len(indent + self.heading) <= width:
53
+ return [indent + self.heading]
54
+ return textwrap.wrap(
55
+ self.heading,
56
+ width=width,
57
+ initial_indent=indent,
58
+ subsequent_indent=indent + " ",
59
+ )
60
+
61
+
62
+ class MarkdownFile:
63
+ HEADING_PATTERN = re.compile(r"""
64
+ ^
65
+ (\#+) \s+ (.*)
66
+ $
67
+ """, re.VERBOSE)
68
+
69
+ VALID_STATES = re.escape("".join(State.all_markers()))
70
+ TASK_PATTERN = re.compile(rf"""
71
+ ^
72
+ (\s*)
73
+ -[ ]
74
+ \[ ([{VALID_STATES}]) \]
75
+ """, re.VERBOSE)
76
+
77
+ def __init__(self, path, base_dir="."):
78
+ self.path = path
79
+ self.base_dir = base_dir
80
+ self.tasks = self.extract_tasks()
81
+
82
+ def extract_tasks(self):
83
+ tasks = []
84
+ for heading, lines in self.sections():
85
+ tasks.extend(self.tasks_in_section(heading, lines))
86
+ return tasks
87
+
88
+ def sections(self):
89
+ heading = None
90
+ lines = []
91
+ results = []
92
+
93
+ # stack stores (level, text) -- explicit level needed for
94
+ # skipped headings (# -> ### -> ##, where position != depth)
95
+ stack = []
96
+
97
+ with open(self.path) as handle:
98
+ for line in handle:
99
+ line = line.rstrip("\n")
100
+ if match := self.HEADING_PATTERN.match(line):
101
+ if lines:
102
+ results.append((heading, lines))
103
+ lines = []
104
+ level = len(match.group(1))
105
+ while stack and stack[-1][0] >= level:
106
+ stack.pop()
107
+ stack.append((level, match.group(2)))
108
+ heading = "# " + " / ".join(text for _, text in stack)
109
+ else:
110
+ lines.append(line)
111
+
112
+ if lines:
113
+ results.append((heading, lines))
114
+
115
+ return results
116
+
117
+ def tasks_in_section(self, heading, lines):
118
+ tasks = []
119
+ index = -1
120
+ while (index := index + 1) < len(lines):
121
+ if match := self.TASK_PATTERN.match(lines[index]):
122
+ text = lines[index].lstrip()
123
+ indent = len(match.group(1)) + 6
124
+ while (
125
+ index + 1 < len(lines)
126
+ and self.is_continuation(lines[index + 1], indent)
127
+ ):
128
+ index += 1
129
+ text += "\n" + lines[index].strip()
130
+ tasks.append(Task(heading, text, State.from_marker(match.group(2))))
131
+
132
+ return tasks
133
+
134
+ def is_continuation(self, line, indent):
135
+ if not line.strip():
136
+ return False
137
+ leading = len(line) - len(line.lstrip())
138
+ return leading == indent
139
+
140
+ @property
141
+ def display_path(self):
142
+ return os.path.relpath(self.path, self.base_dir)
143
+
144
+ @property
145
+ def complete(self):
146
+ return [task for task in self.tasks if task.state == State.COMPLETE]
147
+
148
+ @property
149
+ def incomplete(self):
150
+ return [task for task in self.tasks if task.state == State.INCOMPLETE]
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: whatnext
3
+ Version: 0.0.1
4
+ Summary: A lister of tasks found in Markdown files
5
+ Author: Mark Norman Francis
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/norm/whatnext
8
+ Project-URL: Repository, https://github.com/norm/whatnext
9
+ Project-URL: Documentation, https://github.com/norm/whatnext
10
+ Keywords: cli,markdown,tasks,todo
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENCE
18
+ License-File: LICENSE
19
+ Requires-Dist: toml
20
+ Provides-Extra: test
21
+ Requires-Dist: flake8; extra == "test"
22
+ Requires-Dist: pytest; extra == "test"
23
+ Dynamic: license-file
24
+
25
+ # What next?
26
+
27
+ Document your tasks in Markdown files, using ["standard"][sn] notation:
28
+
29
+ ```markdown
30
+ - [ ] empty, this task is outstanding
31
+ - [X] crossed, this task has been completed
32
+ ```
33
+
34
+ Then install `whatnext`:
35
+
36
+ ```bash
37
+ pip install whatnext
38
+ ```
39
+
40
+ Now run it and it'll tell you what's next:
41
+
42
+ ```bash
43
+ (computer)% whatnext
44
+ README.md:
45
+ # What next?
46
+ - [ ] empty, this task is outstanding
47
+ ```
48
+
49
+ More detail to be found:
50
+
51
+ - [The basics of task formatting](docs/basics.md)
52
+ - [whatnext usage and arguments](docs/usage.md)
53
+ - [The `.whatnext` file](docs/dotwhatnext.md)
54
+
55
+
56
+ ## The reason
57
+
58
+ I like to keep tasks in Markdown files. That way they can be interspersed
59
+ within instructions, serving as reminders, FIXMEs, and other todos.
60
+
61
+
62
+ [sn]: https://blog.github.com/2013-01-09-task-lists-in-gfm-issues-pulls-comments/
@@ -0,0 +1,14 @@
1
+ LICENCE
2
+ LICENSE
3
+ README.md
4
+ pyproject.toml
5
+ tests/test_task.py
6
+ whatnext/__init__.py
7
+ whatnext/cli.py
8
+ whatnext/models.py
9
+ whatnext.egg-info/PKG-INFO
10
+ whatnext.egg-info/SOURCES.txt
11
+ whatnext.egg-info/dependency_links.txt
12
+ whatnext.egg-info/entry_points.txt
13
+ whatnext.egg-info/requires.txt
14
+ whatnext.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ whatnext = whatnext.cli:main
@@ -0,0 +1,5 @@
1
+ toml
2
+
3
+ [test]
4
+ flake8
5
+ pytest
@@ -0,0 +1 @@
1
+ whatnext