powerrules 0.1.0__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.
- powerrules-0.1.0/LICENSE +21 -0
- powerrules-0.1.0/PKG-INFO +138 -0
- powerrules-0.1.0/README.md +113 -0
- powerrules-0.1.0/pyproject.toml +60 -0
- powerrules-0.1.0/src/powerrules/__main__.py +4 -0
- powerrules-0.1.0/src/powerrules/actions/base.py +11 -0
- powerrules-0.1.0/src/powerrules/actions/power.py +70 -0
- powerrules-0.1.0/src/powerrules/application/runtime.py +100 -0
- powerrules-0.1.0/src/powerrules/cli/app.py +114 -0
- powerrules-0.1.0/src/powerrules/cli/errors.py +113 -0
- powerrules-0.1.0/src/powerrules/conditions/base.py +14 -0
- powerrules-0.1.0/src/powerrules/conditions/datetime.py +86 -0
- powerrules-0.1.0/src/powerrules/conditions/operators.py +51 -0
- powerrules-0.1.0/src/powerrules/conditions/process.py +33 -0
- powerrules-0.1.0/src/powerrules/config/builder.py +179 -0
- powerrules-0.1.0/src/powerrules/config/loader.py +28 -0
- powerrules-0.1.0/src/powerrules/config/models.py +212 -0
- powerrules-0.1.0/src/powerrules/engine/exceptions.py +14 -0
- powerrules-0.1.0/src/powerrules/engine/models.py +28 -0
- powerrules-0.1.0/src/powerrules/engine/rule_engine.py +46 -0
- powerrules-0.1.0/src/powerrules/platform/windows/power.py +51 -0
- powerrules-0.1.0/src/powerrules/platform/windows/process.py +20 -0
- powerrules-0.1.0/src/powerrules/providers/clock.py +14 -0
- powerrules-0.1.0/src/powerrules/providers/power.py +19 -0
- powerrules-0.1.0/src/powerrules/providers/process.py +7 -0
powerrules-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 LeoTN
|
|
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,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: powerrules
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A rule-based computer power state management tool
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: shutdown,reboot,hibernate,sleep,cli,windows,linux,macos,rules,policy,manager,power,management
|
|
8
|
+
Author: LeoTN
|
|
9
|
+
Author-email: LeoTN.GitHub@gmx.net
|
|
10
|
+
Requires-Python: >=3.10,<4.0
|
|
11
|
+
Classifier: Topic :: Utilities
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
16
|
+
Requires-Dist: psutil (>=7.0,<8.0)
|
|
17
|
+
Requires-Dist: pydantic (>=2.0,<3.0)
|
|
18
|
+
Requires-Dist: pyyaml (>=6.0,<7.0)
|
|
19
|
+
Requires-Dist: typer (>=0.27.1,<0.28.0)
|
|
20
|
+
Project-URL: Bug Tracker, https://github.com/LeoTN/PowerRules/issues
|
|
21
|
+
Project-URL: Homepage, https://github.com/LeoTN/PowerRules#readme
|
|
22
|
+
Project-URL: Repository, https://github.com/LeoTN/PowerRules
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
<div align="center">
|
|
26
|
+
|
|
27
|
+
[](https://github.com/LeoTN/PowerRules)
|
|
28
|
+
|
|
29
|
+
[](https://github.com/LeoTN/PowerRules/releases/latest)
|
|
30
|
+
[](https://github.com/LeoTN/PowerRules/releases)
|
|
31
|
+
[](https://github.com/LeoTN/PowerRules/blob/main/LICENSE)
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
#
|
|
36
|
+
|
|
37
|
+
* [About](#about)
|
|
38
|
+
* [Getting Started](#getting-started)
|
|
39
|
+
* [Features](#features)
|
|
40
|
+
* [Supported Platforms](#supported-platforms)
|
|
41
|
+
* [Credits & License](#credits--license)
|
|
42
|
+
|
|
43
|
+
## About
|
|
44
|
+
|
|
45
|
+
PowerRules allows you to define rules that automatically control the power state of your computer based on configurable conditions.
|
|
46
|
+
|
|
47
|
+
Rules are evaluated from top to bottom. The first matching rule executes its configured action.
|
|
48
|
+
|
|
49
|
+
## Getting Started
|
|
50
|
+
|
|
51
|
+
**Install with pip:**
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install powerrules
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Create a policy file:**
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
# yaml-language-server: $schema=https://raw.githubusercontent.com/LeoTN/PowerRules/main/assets/schema/powerrules_policy.schema.json
|
|
61
|
+
|
|
62
|
+
rules:
|
|
63
|
+
- name: "Shutdown after nightly weekend backup"
|
|
64
|
+
conditions:
|
|
65
|
+
and:
|
|
66
|
+
- process:
|
|
67
|
+
name: "backup.exe"
|
|
68
|
+
running: false
|
|
69
|
+
- datetime:
|
|
70
|
+
between:
|
|
71
|
+
start: "23"
|
|
72
|
+
end: "1:30"
|
|
73
|
+
- datetime:
|
|
74
|
+
weekday: ["Saturday", "Sunday"]
|
|
75
|
+
action:
|
|
76
|
+
type: shutdown
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Validate the policy:**
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pwru policy validate
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Show configured rules:**
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pwru policy show
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Evaluate the policy once:**
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pwru policy run --once
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Run continuously:**
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pwru policy run
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Use a different policy file with `--policy` or `-p`:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pwru policy run --policy my-policy.yaml
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Features
|
|
110
|
+
|
|
111
|
+
| Feature | Description |
|
|
112
|
+
|---------|-------------|
|
|
113
|
+
| **Rule-based power management** | Define ordered rules with conditions and power actions |
|
|
114
|
+
| **Process conditions** | Match rules based on whether a process is running |
|
|
115
|
+
| **Time conditions** | Match time ranges and weekdays |
|
|
116
|
+
| **Logical conditions** | Combine conditions using `and`, `or`, and `not` |
|
|
117
|
+
| **Power actions** | Shutdown, sleep, hibernate, and reboot |
|
|
118
|
+
| **Continuous evaluation** | Evaluate policies repeatedly |
|
|
119
|
+
| **First-match execution** | Only the first matching rule executes once per match |
|
|
120
|
+
|
|
121
|
+
## Supported Platforms
|
|
122
|
+
|
|
123
|
+
| Platform | Status |
|
|
124
|
+
|----------|:------:|
|
|
125
|
+
| Windows 10/11 | ✅ |
|
|
126
|
+
| Linux | Planned |
|
|
127
|
+
| macOS | Planned |
|
|
128
|
+
|
|
129
|
+
## Credits & License
|
|
130
|
+
|
|
131
|
+
* [Pydantic](https://github.com/pydantic/pydantic) → configuration validation
|
|
132
|
+
* [PyYAML](https://github.com/yaml/pyyaml) → YAML policy parsing
|
|
133
|
+
* [Typer](https://github.com/fastapi/typer) → command-line interface
|
|
134
|
+
* [psutil](https://github.com/giampaolo/psutil) → process information
|
|
135
|
+
* [Inkscape](https://inkscape.org) → program used to design the logo
|
|
136
|
+
|
|
137
|
+
*This repository is licensed under the [MIT License](https://github.com/LeoTN/PowerRules/blob/main/LICENSE).*
|
|
138
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
[](https://github.com/LeoTN/PowerRules)
|
|
4
|
+
|
|
5
|
+
[](https://github.com/LeoTN/PowerRules/releases/latest)
|
|
6
|
+
[](https://github.com/LeoTN/PowerRules/releases)
|
|
7
|
+
[](https://github.com/LeoTN/PowerRules/blob/main/LICENSE)
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
* [About](#about)
|
|
14
|
+
* [Getting Started](#getting-started)
|
|
15
|
+
* [Features](#features)
|
|
16
|
+
* [Supported Platforms](#supported-platforms)
|
|
17
|
+
* [Credits & License](#credits--license)
|
|
18
|
+
|
|
19
|
+
## About
|
|
20
|
+
|
|
21
|
+
PowerRules allows you to define rules that automatically control the power state of your computer based on configurable conditions.
|
|
22
|
+
|
|
23
|
+
Rules are evaluated from top to bottom. The first matching rule executes its configured action.
|
|
24
|
+
|
|
25
|
+
## Getting Started
|
|
26
|
+
|
|
27
|
+
**Install with pip:**
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install powerrules
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Create a policy file:**
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
# yaml-language-server: $schema=https://raw.githubusercontent.com/LeoTN/PowerRules/main/assets/schema/powerrules_policy.schema.json
|
|
37
|
+
|
|
38
|
+
rules:
|
|
39
|
+
- name: "Shutdown after nightly weekend backup"
|
|
40
|
+
conditions:
|
|
41
|
+
and:
|
|
42
|
+
- process:
|
|
43
|
+
name: "backup.exe"
|
|
44
|
+
running: false
|
|
45
|
+
- datetime:
|
|
46
|
+
between:
|
|
47
|
+
start: "23"
|
|
48
|
+
end: "1:30"
|
|
49
|
+
- datetime:
|
|
50
|
+
weekday: ["Saturday", "Sunday"]
|
|
51
|
+
action:
|
|
52
|
+
type: shutdown
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Validate the policy:**
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pwru policy validate
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Show configured rules:**
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pwru policy show
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Evaluate the policy once:**
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pwru policy run --once
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Run continuously:**
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pwru policy run
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Use a different policy file with `--policy` or `-p`:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pwru policy run --policy my-policy.yaml
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Features
|
|
86
|
+
|
|
87
|
+
| Feature | Description |
|
|
88
|
+
|---------|-------------|
|
|
89
|
+
| **Rule-based power management** | Define ordered rules with conditions and power actions |
|
|
90
|
+
| **Process conditions** | Match rules based on whether a process is running |
|
|
91
|
+
| **Time conditions** | Match time ranges and weekdays |
|
|
92
|
+
| **Logical conditions** | Combine conditions using `and`, `or`, and `not` |
|
|
93
|
+
| **Power actions** | Shutdown, sleep, hibernate, and reboot |
|
|
94
|
+
| **Continuous evaluation** | Evaluate policies repeatedly |
|
|
95
|
+
| **First-match execution** | Only the first matching rule executes once per match |
|
|
96
|
+
|
|
97
|
+
## Supported Platforms
|
|
98
|
+
|
|
99
|
+
| Platform | Status |
|
|
100
|
+
|----------|:------:|
|
|
101
|
+
| Windows 10/11 | ✅ |
|
|
102
|
+
| Linux | Planned |
|
|
103
|
+
| macOS | Planned |
|
|
104
|
+
|
|
105
|
+
## Credits & License
|
|
106
|
+
|
|
107
|
+
* [Pydantic](https://github.com/pydantic/pydantic) → configuration validation
|
|
108
|
+
* [PyYAML](https://github.com/yaml/pyyaml) → YAML policy parsing
|
|
109
|
+
* [Typer](https://github.com/fastapi/typer) → command-line interface
|
|
110
|
+
* [psutil](https://github.com/giampaolo/psutil) → process information
|
|
111
|
+
* [Inkscape](https://inkscape.org) → program used to design the logo
|
|
112
|
+
|
|
113
|
+
*This repository is licensed under the [MIT License](https://github.com/LeoTN/PowerRules/blob/main/LICENSE).*
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["poetry-core>=2.0.0"]
|
|
3
|
+
build-backend = "poetry.core.masonry.api"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "powerrules"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A rule-based computer power state management tool"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
license-files = ["LICENSE"]
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.10,<4.0"
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "LeoTN", email = "LeoTN.GitHub@gmx.net" }
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"shutdown",
|
|
18
|
+
"reboot",
|
|
19
|
+
"hibernate",
|
|
20
|
+
"sleep",
|
|
21
|
+
"cli",
|
|
22
|
+
"windows",
|
|
23
|
+
"linux",
|
|
24
|
+
"macos",
|
|
25
|
+
"rules",
|
|
26
|
+
"policy",
|
|
27
|
+
"manager",
|
|
28
|
+
"power",
|
|
29
|
+
"management"
|
|
30
|
+
]
|
|
31
|
+
classifiers = [
|
|
32
|
+
"Topic :: Utilities",
|
|
33
|
+
"Environment :: Console",
|
|
34
|
+
"Programming Language :: Python :: 3",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Operating System :: Microsoft :: Windows"
|
|
37
|
+
]
|
|
38
|
+
dependencies = [
|
|
39
|
+
"pyyaml>=6.0,<7.0",
|
|
40
|
+
"pydantic>=2.0,<3.0",
|
|
41
|
+
"typer>=0.27.1,<0.28.0",
|
|
42
|
+
"psutil>=7.0,<8.0"
|
|
43
|
+
]
|
|
44
|
+
packages = [
|
|
45
|
+
{ include = "powerrules", from = "src" }
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
homepage = "https://github.com/LeoTN/PowerRules#readme"
|
|
50
|
+
repository = "https://github.com/LeoTN/PowerRules"
|
|
51
|
+
"Bug Tracker" = "https://github.com/LeoTN/PowerRules/issues"
|
|
52
|
+
|
|
53
|
+
[project.scripts]
|
|
54
|
+
powerrules = "powerrules.cli.app:app"
|
|
55
|
+
pwru = "powerrules.cli.app:app"
|
|
56
|
+
|
|
57
|
+
[dependency-groups]
|
|
58
|
+
dev = [
|
|
59
|
+
"pytest>=8.0,<9.0"
|
|
60
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from powerrules.engine.exceptions import ActionExecutionError
|
|
2
|
+
from powerrules.providers.power import PowerProvider
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ShutdownAction:
|
|
6
|
+
def __init__(self, power_provider: PowerProvider):
|
|
7
|
+
self.power_provider = power_provider
|
|
8
|
+
|
|
9
|
+
def execute(self) -> None:
|
|
10
|
+
"""Shut down the computer.
|
|
11
|
+
|
|
12
|
+
Raises:
|
|
13
|
+
ActionExecutionError: If the computer cannot be shut down.
|
|
14
|
+
"""
|
|
15
|
+
try:
|
|
16
|
+
self.power_provider.shutdown()
|
|
17
|
+
except Exception as e:
|
|
18
|
+
raise ActionExecutionError("Failed to shut down the computer") from e
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SleepAction:
|
|
22
|
+
def __init__(self, power_provider: PowerProvider):
|
|
23
|
+
self.power_provider = power_provider
|
|
24
|
+
|
|
25
|
+
def execute(self) -> None:
|
|
26
|
+
"""Put the computer into sleep mode.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ActionExecutionError: If the computer cannot be put into sleep mode.
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
self.power_provider.sleep()
|
|
33
|
+
except Exception as e:
|
|
34
|
+
raise ActionExecutionError(
|
|
35
|
+
"Failed to put the computer into sleep mode"
|
|
36
|
+
) from e
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class HibernateAction:
|
|
40
|
+
def __init__(self, power_provider: PowerProvider):
|
|
41
|
+
self.power_provider = power_provider
|
|
42
|
+
|
|
43
|
+
def execute(self) -> None:
|
|
44
|
+
"""Put the computer into hibernation.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
ActionExecutionError: If the computer cannot be put into hibernation.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
self.power_provider.hibernate()
|
|
51
|
+
except Exception as e:
|
|
52
|
+
raise ActionExecutionError(
|
|
53
|
+
"Failed to put the computer into hibernation"
|
|
54
|
+
) from e
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RebootAction:
|
|
58
|
+
def __init__(self, power_provider: PowerProvider):
|
|
59
|
+
self.power_provider = power_provider
|
|
60
|
+
|
|
61
|
+
def execute(self) -> None:
|
|
62
|
+
"""Reboot the computer.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ActionExecutionError: If the computer cannot be rebooted.
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
self.power_provider.reboot()
|
|
69
|
+
except Exception as e:
|
|
70
|
+
raise ActionExecutionError("Failed to reboot the computer") from e
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from powerrules.config.builder import ConfigurationBuilder
|
|
5
|
+
from powerrules.config.loader import ConfigurationLoader
|
|
6
|
+
from powerrules.engine.models import Rule, RuleEvaluationResult
|
|
7
|
+
from powerrules.engine.rule_engine import RuleEngine
|
|
8
|
+
from powerrules.platform.windows.power import WindowsPowerProvider
|
|
9
|
+
from powerrules.platform.windows.process import WindowsProcessProvider
|
|
10
|
+
from powerrules.providers.clock import SystemClockProvider
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PowerRulesRuntime:
|
|
14
|
+
"""Coordinate PowerRules configuration loading and rule evaluation and action execution."""
|
|
15
|
+
|
|
16
|
+
def run_once(self, configuration_path: Path) -> RuleEvaluationResult:
|
|
17
|
+
"""Load and evaluate the configured policy once.
|
|
18
|
+
|
|
19
|
+
The policy is loaded only for this evaluation.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
configuration_path: Path to the PowerRules policy file.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The result of the rule evaluation.
|
|
26
|
+
"""
|
|
27
|
+
rule_engine = self._build_rule_engine(configuration_path)
|
|
28
|
+
|
|
29
|
+
return rule_engine.evaluate()
|
|
30
|
+
|
|
31
|
+
def run_continuously(
|
|
32
|
+
self,
|
|
33
|
+
configuration_path: Path,
|
|
34
|
+
evaluation_interval: float = 10.0,
|
|
35
|
+
stop_on_match: bool = False,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Load a policy once and continuously evaluate it.
|
|
38
|
+
|
|
39
|
+
The policy is loaded only once when the method starts.
|
|
40
|
+
Changes to the policy file are ignored until the process is restarted.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
configuration_path: Path to the PowerRules policy file.
|
|
44
|
+
evaluation_interval: Delay between evaluations in seconds.
|
|
45
|
+
stop_on_match: Stop the evaluation when a rule matches.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ValueError: If the evaluation interval is less than or equal to zero.
|
|
49
|
+
ConditionEvaluationError: If a condition cannot be evaluated.
|
|
50
|
+
ActionExecutionError: If a matching action cannot be executed.
|
|
51
|
+
"""
|
|
52
|
+
if evaluation_interval <= 0:
|
|
53
|
+
raise ValueError("Evaluation interval must be greater than zero")
|
|
54
|
+
|
|
55
|
+
rule_engine = self._build_rule_engine(configuration_path)
|
|
56
|
+
last_matched_rule: Rule | None = None
|
|
57
|
+
|
|
58
|
+
while True:
|
|
59
|
+
matched_rule = rule_engine.find_match()
|
|
60
|
+
|
|
61
|
+
if matched_rule is None:
|
|
62
|
+
last_matched_rule = None
|
|
63
|
+
|
|
64
|
+
elif matched_rule is not last_matched_rule:
|
|
65
|
+
matched_rule.action.execute()
|
|
66
|
+
last_matched_rule = matched_rule
|
|
67
|
+
|
|
68
|
+
if stop_on_match:
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
time.sleep(evaluation_interval)
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def _build_rule_engine(configuration_path: Path) -> RuleEngine:
|
|
75
|
+
"""Build a rule engine from a policy file.
|
|
76
|
+
|
|
77
|
+
The policy is loaded and built exactly once per invocation.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
configuration_path: Path to the PowerRules policy file.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
A configured rule engine.
|
|
84
|
+
"""
|
|
85
|
+
configuration = ConfigurationLoader().load(configuration_path)
|
|
86
|
+
|
|
87
|
+
clock_provider = SystemClockProvider()
|
|
88
|
+
process_provider = WindowsProcessProvider()
|
|
89
|
+
power_provider = WindowsPowerProvider()
|
|
90
|
+
|
|
91
|
+
rule_set = ConfigurationBuilder(
|
|
92
|
+
# Information about the current date and time
|
|
93
|
+
clock_provider=clock_provider,
|
|
94
|
+
# Information about running processes
|
|
95
|
+
process_provider=process_provider,
|
|
96
|
+
# Basically an API to interact with the power state of the OS
|
|
97
|
+
power_provider=power_provider,
|
|
98
|
+
).build(configuration)
|
|
99
|
+
|
|
100
|
+
return RuleEngine(rule_set.rules)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from powerrules.application.runtime import PowerRulesRuntime
|
|
7
|
+
from powerrules.cli.errors import cli_command
|
|
8
|
+
from powerrules.config.loader import ConfigurationLoader
|
|
9
|
+
|
|
10
|
+
# Main application
|
|
11
|
+
app = typer.Typer(
|
|
12
|
+
name="pwru",
|
|
13
|
+
help="A rule-based computer power state management tool.",
|
|
14
|
+
no_args_is_help=True,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Policy subcommand
|
|
18
|
+
policy_app = typer.Typer(
|
|
19
|
+
name="policy",
|
|
20
|
+
help="Manage PowerRules policies.",
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
app.add_typer(policy_app, name="policy")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def version_callback(value: bool) -> None:
|
|
28
|
+
"""Display the installed PowerRules version."""
|
|
29
|
+
if value:
|
|
30
|
+
typer.echo(f"PowerRules {version('powerrules')}")
|
|
31
|
+
raise typer.Exit()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.callback()
|
|
35
|
+
def main(
|
|
36
|
+
version: bool = typer.Option(
|
|
37
|
+
False,
|
|
38
|
+
"--version",
|
|
39
|
+
help="Display the installed PowerRules version.",
|
|
40
|
+
callback=version_callback,
|
|
41
|
+
is_eager=True,
|
|
42
|
+
),
|
|
43
|
+
) -> None:
|
|
44
|
+
"""A rule-based computer power state management tool."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@policy_app.command("validate")
|
|
48
|
+
@cli_command
|
|
49
|
+
def validate(
|
|
50
|
+
policy: Path = typer.Option(
|
|
51
|
+
Path("powerrules.yaml"),
|
|
52
|
+
"--policy",
|
|
53
|
+
"-p",
|
|
54
|
+
help="Path to the PowerRules policy file.",
|
|
55
|
+
),
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Validate a PowerRules policy file."""
|
|
58
|
+
ConfigurationLoader().load(policy)
|
|
59
|
+
|
|
60
|
+
typer.echo("[INFO] Policy is valid")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@policy_app.command("show")
|
|
64
|
+
@cli_command
|
|
65
|
+
def show(
|
|
66
|
+
policy: Path = typer.Option(
|
|
67
|
+
Path("powerrules.yaml"),
|
|
68
|
+
"--policy",
|
|
69
|
+
"-p",
|
|
70
|
+
help="Path to the PowerRules policy file.",
|
|
71
|
+
),
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Display the configured rules of a PowerRules policy."""
|
|
74
|
+
policy_configuration = ConfigurationLoader().load(policy)
|
|
75
|
+
|
|
76
|
+
for index, rule in enumerate(policy_configuration.rules, start=1):
|
|
77
|
+
status = "enabled" if rule.enabled else "disabled"
|
|
78
|
+
typer.echo(f"{index}. {rule.name} [{status}]")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@policy_app.command("run")
|
|
82
|
+
@cli_command
|
|
83
|
+
def run(
|
|
84
|
+
once: bool = typer.Option(
|
|
85
|
+
False,
|
|
86
|
+
"--once",
|
|
87
|
+
help="Evaluate the policy once and then exit.",
|
|
88
|
+
),
|
|
89
|
+
stop_on_match: bool = typer.Option(
|
|
90
|
+
False,
|
|
91
|
+
"--stop-on-match",
|
|
92
|
+
help="Stop the continuous evaluation after the first rule match.",
|
|
93
|
+
),
|
|
94
|
+
policy: Path = typer.Option(
|
|
95
|
+
Path("powerrules.yaml"),
|
|
96
|
+
"--policy",
|
|
97
|
+
"-p",
|
|
98
|
+
help="Path to the PowerRules policy file.",
|
|
99
|
+
),
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Evaluate a PowerRules policy continuously or once."""
|
|
102
|
+
runtime = PowerRulesRuntime()
|
|
103
|
+
|
|
104
|
+
if once:
|
|
105
|
+
result = runtime.run_once(configuration_path=policy)
|
|
106
|
+
|
|
107
|
+
if result.matched_rule is None:
|
|
108
|
+
typer.echo("[INFO] No rule matched")
|
|
109
|
+
else:
|
|
110
|
+
typer.echo(f"[INFO] Rule '{result.matched_rule.name}' matched")
|
|
111
|
+
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
runtime.run_continuously(configuration_path=policy, stop_on_match=stop_on_match)
|