Coverage for flogin/__main__.py: 0%
115 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-03 22:51 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-03 22:51 +0000
1import argparse
2import importlib
3import importlib.metadata
4import json
5import platform
6import sys
7import uuid
8from pathlib import Path
10from . import version_info
13def show_version() -> None:
14 entries: list[str] = []
16 entries.append(
17 f"- Python v{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}-{sys.version_info.releaselevel}"
18 )
20 entries.append(
21 f"- flogin v{version_info.major}.{version_info.minor}.{version_info.micro}-{version_info.releaselevel}"
22 )
24 try:
25 version = importlib.metadata.version("flogin")
26 if version:
27 entries.append(f" - flogin metadata: v{version}")
28 except importlib.metadata.PackageNotFoundError:
29 pass
31 uname = platform.uname()
32 entries.append(f"- system info: {uname.system} {uname.release} {uname.version}")
33 print("\n".join(entries))
36def core(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None:
37 if args.version:
38 show_version()
39 else:
40 parser.print_help()
43_settings_template = """
44body:
45 - type: textBlock
46 attributes:
47 description: Welcome to the settings page for my plugin. Here you can configure the plugin to your liking.
48 - type: input
49 attributes:
50 name: user_name
51 label: How should I call you?
52 defaultValue: the user
53 - type: textarea
54 attributes:
55 name: prepend_result
56 label: Text to prepend to result output
57 description: >
58 This text will be added to the beginning of the result output. For example, if you set this to
59 "The result is: ", and the result is "42", the output will be "The result is: 42".
60 - type: dropdown
61 attributes:
62 name: programming_language
63 label: Programming language to prefer for answers
64 defaultValue: TypeScript
65 options:
66 - JavaScript
67 - TypeScript
68 - Python
69 - "C#"
70 - type: checkbox
71 attributes:
72 name: prefer_shorter_aswers
73 label: Prefer shorter answers
74 description: If checked, the plugin will try to give answer much shorter than the usual ones.
75 defaultValue: false
76"""
77_gh_bug_report_issue_template = """
78name: Bug Report
79description: Report broken or incorrect behaviour
80labels: unconfirmed bug
81body:
82 - type: markdown
83 attributes:
84 value: >
85 Thanks for taking the time to fill out a bug.
87 Please note that this form is for bugs only!
88 - type: input
89 attributes:
90 label: Summary
91 description: A simple summary of your bug report
92 validations:
93 required: true
94 - type: textarea
95 attributes:
96 label: Reproduction Steps
97 description: >
98 What you did to make it happen.
99 validations:
100 required: true
101 - type: textarea
102 attributes:
103 label: Minimal Reproducible Code
104 description: >
105 A short snippet of code that showcases the bug.
106 render: python
107 - type: textarea
108 attributes:
109 label: Expected Results
110 description: >
111 What did you expect to happen?
112 validations:
113 required: true
114 - type: textarea
115 attributes:
116 label: Actual Results
117 description: >
118 What actually happened?
119 validations:
120 required: true
121 - type: textarea
122 attributes:
123 label: Flow Launcher Version
124 description: Go into your flow launcher settings, go into the about section, and the version should be at the top.
125 validations:
126 required: true
127 - type: textarea
128 attributes:
129 label: Python Version/Path
130 description: Go into your flow launcher settings, go to the general section, and scroll down until you find the `Python Path` field. Copy and paste the value here.
131 validations:
132 required: true
133 - type: textarea
134 attributes:
135 label: If applicable, Flow Launcher Log File
136 description: Use the `Open Log Location` command with the `System Commands` plugin to open the log file folder, and upload the newest file here.
137 - type: textarea
138 attributes:
139 label: Flogin Log File
140 description: Use the `Flow Launcher UserData Folder` command with the `System Commands` plugin to open your userdata folder, go into the `Plugins` folder, then find the plugin and go into it. If the `flogin.log` file exists, upload it here. Otherwise please state that it was not there.
141 - type: checkboxes
142 attributes:
143 label: Checklist
144 description: >
145 Let's make sure you've properly done due diligence when reporting this issue!
146 options:
147 - label: I have searched the open issues for duplicates.
148 required: true
149 - label: I have shown the entire traceback, if possible.
150 required: true
151 - label: I have removed my token from display, if visible.
152 required: true
153 - type: textarea
154 attributes:
155 label: Additional Context
156 description: If there is anything else to say, please do so here.
157"""
158_gh_pr_template = """
159## Summary
161<!-- What is this pull request for? Does it fix any issues? -->
163## Checklist
165<!-- Put an x inside [ ] to check it, like so: [x] -->
167- [ ] If code changes were made then they have been tested.
168 - [ ] I have updated the documentation to reflect the changes.
169- [ ] This PR fixes an issue.
170- [ ] This PR adds something new (e.g. new method or parameters).
171- [ ] This PR is a breaking change (e.g. methods or parameters removed/renamed)
172- [ ] This PR is **not** a code change (e.g. documentation, README, ...)
173"""
174_gh_publish_workflow = """
175name: Publish and Release
177on:
178 workflow_dispatch:
180jobs:
181 publish:
182 runs-on: ubuntu-latest
183 env:
184 python_ver: 3.11
185 permissions:
186 contents: write
188 steps:
189 - uses: actions/checkout@v2
191 - name: Set up Python ${{ env.python_ver }}
192 uses: actions/setup-python@v2
193 with:
194 python-version: ${{ env.python_ver }}
196 - name: get version
197 id: version
198 uses: notiz-dev/github-action-json-property@release
199 with:
200 path: 'plugin.json'
201 prop_path: 'Version'
203 - run: echo ${{steps.version.outputs.prop}}
205 - name: Install dependencies
206 run: |
207 python -m pip install --upgrade pip
208 pip install -r ./requirements.txt -t ./lib
209 zip -r ${{ github.event.repository.name }}.zip . -x '*.git*'
211 - name: Publish
212 if: success()
213 uses: softprops/action-gh-release@v2
214 with:
215 files: '${{ github.event.repository.name }}.zip'
216 tag_name: "v${{steps.version.outputs.prop}}"
217"""
218_plugin_dot_py_template = """
219from flogin import Plugin
221from .handlers.root import RootHandler
222from .settings import {plugin}Settings
225class {plugin}Plugin(Plugin[{plugin}Settings]):
226 def __init__(self) -> None:
227 super().__init__()
229 self.register_search_handler(RootHandler())
230"""
231_plugin_dot_py_template_no_settings = """
232from flogin import Plugin
234from .handlers.root import RootHandler
237class {plugin}Plugin(Plugin):
238 def __init__(self) -> None:
239 super().__init__()
241 self.register_search_handler(RootHandler())
242"""
243_settings_dot_py_template = """
244from flogin import Settings
247class {plugin}Settings(Settings):
248 ...
249"""
250_handler_template = """
251from __future__ import annotations
253from typing import TYPE_CHECKING
255from flogin import Query, Result, SearchHandler
257if TYPE_CHECKING:
258 from ..plugin import {plugin}Plugin
261class {name}Handler(SearchHandler["{plugin}Plugin"]):
262 def condition(self, query: Query) -> bool:
263 return True
265 async def callback(self, query: Query):
266 return "Hello World!"
267"""
268_main_py_template = """
269import os
270import sys
272parent_folder_path = os.path.abspath(os.path.dirname(__file__))
273sys.path.append(parent_folder_path)
274sys.path.append(os.path.join(parent_folder_path, "lib"))
275sys.path.append(os.path.join(parent_folder_path, "venv", "lib", "site-packages"))
277from plugin.plugin import {plugin}Plugin
279if __name__ == "__main__":
280 {plugin}Plugin().run()
281"""
284def create_plugin_dot_json_file(
285 parser: argparse.ArgumentParser, plugin_name: str
286) -> None:
287 data = {
288 "ID": str(uuid.uuid4()),
289 "ActionKeyword": "*",
290 "Name": plugin_name,
291 "Description": "",
292 "Author": "",
293 "Version": "0.0.1",
294 "Language": "python_v2",
295 "Website": "https://github.com/author/Flow.Launcher.Plugin.Name",
296 "IcoPath": "Images/app.png",
297 "ExecuteFileName": "main.py",
298 }
299 write_to_file(Path("plugin.json"), json.dumps(data, indent=4), parser)
302def write_to_file(path: Path, content: str, parser: argparse.ArgumentParser) -> bool:
303 path.parent.mkdir(parents=True, exist_ok=True)
305 try:
306 with path.open("w") as f:
307 f.write(content.strip())
308 except OSError as e:
309 parser.error(f"Unable to write to {path}: {e}")
310 return False
311 else:
312 print(f"Wrote to {path}")
313 return True
316def create_new_handler(
317 parser: argparse.ArgumentParser, *, path: Path, name: str, plugin: str
318) -> None:
319 write_to_file(path, _handler_template.format(plugin=plugin, name=name), parser)
322def create_plugin_directory(
323 parser: argparse.ArgumentParser, args: argparse.Namespace
324) -> None:
325 plugin_dir = Path("plugin")
326 plugin_name = args.plugin_name
328 main_file = Path("main.py")
330 write_to_file(main_file, _main_py_template.format(plugin=plugin_name), parser)
332 plugin_file = plugin_dir / "plugin.py"
333 template = (
334 _plugin_dot_py_template_no_settings
335 if args.no_settings
336 else _plugin_dot_py_template
337 )
338 write_to_file(plugin_file, template.format(plugin=plugin_name), parser)
340 if not args.no_settings:
341 settings_file = plugin_dir / "settings.py"
342 write_to_file(
343 settings_file, _settings_dot_py_template.format(plugin=plugin_name), parser
344 )
346 handlers_dir = plugin_dir / "handlers"
347 root_handler_file = handlers_dir / "root.py"
349 create_new_handler(parser, path=root_handler_file, name="Root", plugin=plugin_name)
352def create_git_files(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None:
353 github_dir = Path(".github")
354 issue_template_dir = github_dir / "ISSUE_TEMPLATE"
355 workflows_dir = github_dir / "workflows"
357 bug_report_template_file = issue_template_dir / "bug_report.yml"
358 write_to_file(bug_report_template_file, _gh_bug_report_issue_template, parser)
360 pr_template_file = github_dir / "PULL_REQUEST_TEMPLATE.md"
361 write_to_file(pr_template_file, _gh_pr_template, parser)
363 publish_file = workflows_dir / "publish_release.yml"
364 write_to_file(publish_file, _gh_publish_workflow, parser)
367def init_command(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None:
368 plugin_name = args.plugin_name
370 if not args.no_manifest:
371 create_plugin_dot_json_file(parser, plugin_name)
373 if not args.no_settings:
374 settings_file = Path("SettingsTemplate.yaml")
375 write_to_file(settings_file, _settings_template, parser)
377 if not args.no_git:
378 create_git_files(parser, args)
380 if not args.no_plugin:
381 create_plugin_directory(parser, args)
384def add_init_args(
385 subparser: "argparse._SubParsersAction[argparse.ArgumentParser]",
386) -> None:
387 parser = subparser.add_parser(
388 "init",
389 help="quickly sets up the environment for the development of a new plugin",
390 )
391 parser.set_defaults(func=init_command)
393 parser.add_argument("plugin_name", help="the name of the plugin")
394 parser.add_argument(
395 "--no-git", help="whether or not to add git files", action="store_true"
396 )
397 parser.add_argument(
398 "--no-settings", help="whether or not to add setting files", action="store_true"
399 )
400 parser.add_argument(
401 "--no-plugin", help="Do not create an example plugin", action="store_true"
402 )
403 parser.add_argument(
404 "--no-manifest",
405 help="Do not create a plugin.json manifest file",
406 action="store_true",
407 )
410def add_new_handler_command(
411 parser: argparse.ArgumentParser, args: argparse.Namespace
412) -> None:
413 plugin_name: str = args.plugin_name or ""
414 handler_name: str = args.name
415 dir = Path("plugin") / (args.dir or "handlers")
416 path = dir / handler_name.lower()
418 classname = handler_name.replace("_", " ").title().replace(" ", "")
420 create_new_handler(
421 parser, path=path.with_suffix(".py"), name=classname, plugin=plugin_name
422 )
425def add_handler_command_args(
426 subparser: "argparse._SubParsersAction[argparse.ArgumentParser]",
427) -> None:
428 parser = subparser.add_parser(
429 "new-handler",
430 help="quickly set up a new handler using flogin's handler template.",
431 )
432 parser.set_defaults(func=add_new_handler_command)
434 parser.add_argument(
435 "name", help="the name of the handler in case snake format. ex: 'root'"
436 )
437 parser.add_argument("--plugin-name", help="Sets the plugin name for importing")
438 parser.add_argument("--dir", help="The handlers dir. Defaults to 'handlers'")
441def parse_args() -> tuple[argparse.ArgumentParser, argparse.Namespace]:
442 parser = argparse.ArgumentParser(
443 prog="flogin", description="Tools for helping with plugin development"
444 )
445 parser.add_argument(
446 "-v", "--version", action="store_true", help="shows the library version"
447 )
448 parser.set_defaults(func=core)
450 subparser = parser.add_subparsers(dest="subcommand", title="subcommands")
451 add_init_args(subparser)
452 add_handler_command_args(subparser)
453 return parser, parser.parse_args()
456def main() -> None:
457 parser, args = parse_args()
458 args.func(parser, args)
461if __name__ == "__main__":
462 main()