mtcli-range 1.0.0.dev0__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.
- mtcli_range-1.0.0.dev0/LICENSE +21 -0
- mtcli_range-1.0.0.dev0/PKG-INFO +57 -0
- mtcli_range-1.0.0.dev0/README.md +28 -0
- mtcli_range-1.0.0.dev0/mtcli_range/__init__.py +0 -0
- mtcli_range-1.0.0.dev0/mtcli_range/cli.py +54 -0
- mtcli_range-1.0.0.dev0/mtcli_range/conf.py +41 -0
- mtcli_range-1.0.0.dev0/mtcli_range/plugin.py +6 -0
- mtcli_range-1.0.0.dev0/mtcli_range/range_controller.py +65 -0
- mtcli_range-1.0.0.dev0/mtcli_range/range_model.py +103 -0
- mtcli_range-1.0.0.dev0/mtcli_range/range_view.py +66 -0
- mtcli_range-1.0.0.dev0/pyproject.toml +81 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Valmir França
|
|
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,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mtcli-range
|
|
3
|
+
Version: 1.0.0.dev0
|
|
4
|
+
Summary: Range bars plugin para mtcli
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: trading,range,metatrader5,mt5,cli,price-action,tape-reading
|
|
8
|
+
Author: Valmir França
|
|
9
|
+
Author-email: vfranca3@gmail.com
|
|
10
|
+
Requires-Python: >=3.10,<3.14
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
20
|
+
Requires-Dist: click (>=8.3.0,<9.0.0)
|
|
21
|
+
Requires-Dist: metatrader5 (>=5.0.5370,<6.0.0)
|
|
22
|
+
Requires-Dist: mtcli (==3.5.*)
|
|
23
|
+
Project-URL: Documentation, https://vfranca.github.io/mtcli-range
|
|
24
|
+
Project-URL: Homepage, https://github.com/vfranca/mtcli-range
|
|
25
|
+
Project-URL: Issues, https://github.com/vfranca/mtcli-range/issues
|
|
26
|
+
Project-URL: Repository, https://github.com/vfranca/mtcli-range
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
\# plugin-exemplo
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Plugin do mtcli que adiciona o comando plugin de exemplo.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
\## Instalação
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
```cmd
|
|
46
|
+
|
|
47
|
+
git clone git@github.com:vfranca/plugin-exemplo.git
|
|
48
|
+
|
|
49
|
+
cd plugin-exemplo
|
|
50
|
+
|
|
51
|
+
pip install .
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
\# plugin-exemplo
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
Plugin do mtcli que adiciona o comando plugin de exemplo.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
\## Instalação
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
```cmd
|
|
18
|
+
|
|
19
|
+
git clone git@github.com:vfranca/plugin-exemplo.git
|
|
20
|
+
|
|
21
|
+
cd plugin-exemplo
|
|
22
|
+
|
|
23
|
+
pip install .
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interface CLI do plugin mtcli-range.
|
|
3
|
+
|
|
4
|
+
Comando:
|
|
5
|
+
mt range
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from mtcli.logger import setup_logger
|
|
10
|
+
from .range_controller import RangeController
|
|
11
|
+
from .conf import (
|
|
12
|
+
get_default_symbol,
|
|
13
|
+
get_default_range,
|
|
14
|
+
get_default_timeframe,
|
|
15
|
+
get_default_bars,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
log = setup_logger("mtcli.range.cli")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.command("range")
|
|
22
|
+
@click.version_option(package_name="mtcli-range")
|
|
23
|
+
@click.option("-s", "--symbol", default=get_default_symbol(), show_default=True, help="Ativo.")
|
|
24
|
+
@click.option("-t", "--timeframe", default=get_default_timeframe(), show_default=True, help="Timeframe base.")
|
|
25
|
+
@click.option("-b", "--bars", default=get_default_bars(), show_default=True, help="Quantidade de candles base.")
|
|
26
|
+
@click.option("-r", "--range-size", default=get_default_range(), show_default=True, help="Tamanho do range.")
|
|
27
|
+
@click.option("--ancorar-abertura", is_flag=True, help="Inicia o Range a partir da abertura do primeiro candle.")
|
|
28
|
+
@click.option("--numerar", is_flag=True, help="Numera as linhas exibidas.")
|
|
29
|
+
def range_command(symbol, timeframe, bars, range_size, ancorar_abertura, numerar):
|
|
30
|
+
"""
|
|
31
|
+
Exibe gráfico de Range no terminal.
|
|
32
|
+
|
|
33
|
+
Exemplos:
|
|
34
|
+
|
|
35
|
+
mt range -s WING26 -t m5 -b 1000 -r 60
|
|
36
|
+
mt range --ancorar-abertura
|
|
37
|
+
mt range --numerar
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
controller = RangeController(
|
|
42
|
+
symbol=symbol,
|
|
43
|
+
timeframe=timeframe,
|
|
44
|
+
bars=bars,
|
|
45
|
+
range_size=range_size,
|
|
46
|
+
ancorar_abertura=ancorar_abertura,
|
|
47
|
+
numerar=numerar,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
controller.executar()
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
log.error(f"Erro ao executar mt range: {e}")
|
|
54
|
+
click.echo(f"Erro: {e}")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuração do plugin mtcli-range.
|
|
3
|
+
|
|
4
|
+
Lê parâmetros do arquivo mtcli.ini na seção [range].
|
|
5
|
+
Permite definir valores padrão para:
|
|
6
|
+
- símbolo
|
|
7
|
+
- tamanho do range
|
|
8
|
+
- timeframe base
|
|
9
|
+
- quantidade de candles
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import configparser
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
CONFIG_PATH = os.path.join(os.getcwd(), "mtcli.ini")
|
|
17
|
+
|
|
18
|
+
config = configparser.ConfigParser()
|
|
19
|
+
config.read(CONFIG_PATH)
|
|
20
|
+
|
|
21
|
+
SECTION = "range"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_default_symbol() -> str:
|
|
25
|
+
"""Retorna símbolo padrão do arquivo .ini."""
|
|
26
|
+
return config.get(SECTION, "symbol", fallback="WIN$")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_default_range() -> float:
|
|
30
|
+
"""Retorna tamanho do range padrão."""
|
|
31
|
+
return config.getfloat(SECTION, "range_size", fallback=50.0)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_default_timeframe() -> str:
|
|
35
|
+
"""Retorna timeframe base padrão."""
|
|
36
|
+
return config.get(SECTION, "timeframe", fallback="m5")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_default_bars() -> int:
|
|
40
|
+
"""Retorna quantidade padrão de candles base."""
|
|
41
|
+
return config.getint(SECTION, "bars", fallback=500)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Controller do plugin mtcli-range.
|
|
3
|
+
|
|
4
|
+
Orquestra:
|
|
5
|
+
- Conexão MT5
|
|
6
|
+
- Model
|
|
7
|
+
- View
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from mtcli.mt5_context import mt5_conexao
|
|
11
|
+
from mtcli.logger import setup_logger
|
|
12
|
+
from mtcli.domain.timeframe import Timeframe
|
|
13
|
+
from .range_model import RangeModel
|
|
14
|
+
from .range_view import RangeView
|
|
15
|
+
|
|
16
|
+
log = setup_logger("mtcli.range.controller")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RangeController:
|
|
20
|
+
"""
|
|
21
|
+
Controlador principal do fluxo.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
symbol: str,
|
|
27
|
+
timeframe: str,
|
|
28
|
+
bars: int,
|
|
29
|
+
range_size: float,
|
|
30
|
+
ancorar_abertura: bool,
|
|
31
|
+
numerar: bool,
|
|
32
|
+
):
|
|
33
|
+
self.symbol = symbol
|
|
34
|
+
self.timeframe = Timeframe.from_string(timeframe)
|
|
35
|
+
self.bars = bars
|
|
36
|
+
self.range_size = range_size
|
|
37
|
+
self.ancorar_abertura = ancorar_abertura
|
|
38
|
+
self.numerar = numerar
|
|
39
|
+
|
|
40
|
+
def executar(self):
|
|
41
|
+
"""
|
|
42
|
+
Executa o fluxo completo do gráfico Range.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
with mt5_conexao():
|
|
46
|
+
model = RangeModel(
|
|
47
|
+
self.symbol,
|
|
48
|
+
self.timeframe.mt5_const,
|
|
49
|
+
self.bars,
|
|
50
|
+
self.range_size,
|
|
51
|
+
ancorar_abertura=self.ancorar_abertura,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
blocos = model.gerar_range()
|
|
55
|
+
|
|
56
|
+
RangeView.exibir_header(
|
|
57
|
+
self.symbol,
|
|
58
|
+
self.range_size,
|
|
59
|
+
self.ancorar_abertura,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
RangeView.exibir_blocos(
|
|
63
|
+
blocos,
|
|
64
|
+
numerar=self.numerar,
|
|
65
|
+
)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Model responsável pela geração dos blocos Range.
|
|
3
|
+
|
|
4
|
+
O Range é baseado em movimentação de preço fixa (range_size).
|
|
5
|
+
Um novo bloco é criado somente quando o preço se move
|
|
6
|
+
range_size pontos acima ou abaixo do fechamento (ou abertura ancorada).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import List
|
|
11
|
+
import MetaTrader5 as mt5
|
|
12
|
+
from mtcli.logger import setup_logger
|
|
13
|
+
|
|
14
|
+
log = setup_logger("mtcli.range.model")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class RangeBlock:
|
|
19
|
+
"""Representa um bloco de Range."""
|
|
20
|
+
open: float
|
|
21
|
+
close: float
|
|
22
|
+
direction: str # "UP" ou "DOWN"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RangeModel:
|
|
26
|
+
"""
|
|
27
|
+
Responsável por:
|
|
28
|
+
- Buscar dados no MT5
|
|
29
|
+
- Calcular blocos de Range
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
symbol: str,
|
|
35
|
+
timeframe_const: int,
|
|
36
|
+
bars: int,
|
|
37
|
+
range_size: float,
|
|
38
|
+
ancorar_abertura: bool = False,
|
|
39
|
+
):
|
|
40
|
+
self.symbol = symbol
|
|
41
|
+
self.timeframe_const = timeframe_const
|
|
42
|
+
self.bars = bars
|
|
43
|
+
self.range_size = range_size
|
|
44
|
+
self.ancorar_abertura = ancorar_abertura
|
|
45
|
+
|
|
46
|
+
def obter_candles(self):
|
|
47
|
+
"""Obtém candles do MT5."""
|
|
48
|
+
log.info(f"Buscando {self.bars} candles de {self.symbol}")
|
|
49
|
+
rates = mt5.copy_rates_from_pos(
|
|
50
|
+
self.symbol,
|
|
51
|
+
self.timeframe_const,
|
|
52
|
+
0,
|
|
53
|
+
self.bars
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if rates is None:
|
|
57
|
+
raise RuntimeError("Erro ao obter dados do MT5.")
|
|
58
|
+
|
|
59
|
+
return rates
|
|
60
|
+
|
|
61
|
+
def gerar_range(self) -> List[RangeBlock]:
|
|
62
|
+
"""
|
|
63
|
+
Constrói blocos de Range a partir dos candles base.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
rates = self.obter_candles()
|
|
67
|
+
|
|
68
|
+
if len(rates) == 0:
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
blocos: List[RangeBlock] = []
|
|
72
|
+
|
|
73
|
+
# 🔹 Âncora inicial
|
|
74
|
+
if self.ancorar_abertura:
|
|
75
|
+
ultimo_preco_base = rates[0]["open"]
|
|
76
|
+
log.info("Range ancorado na abertura do primeiro candle.")
|
|
77
|
+
else:
|
|
78
|
+
ultimo_preco_base = rates[0]["close"]
|
|
79
|
+
log.info("Range iniciado a partir do fechamento do primeiro candle.")
|
|
80
|
+
|
|
81
|
+
for candle in rates[1:]:
|
|
82
|
+
preco = candle["close"]
|
|
83
|
+
|
|
84
|
+
while abs(preco - ultimo_preco_base) >= self.range_size:
|
|
85
|
+
|
|
86
|
+
if preco > ultimo_preco_base:
|
|
87
|
+
novo_close = ultimo_preco_base + self.range_size
|
|
88
|
+
direcao = "UP"
|
|
89
|
+
else:
|
|
90
|
+
novo_close = ultimo_preco_base - self.range_size
|
|
91
|
+
direcao = "DOWN"
|
|
92
|
+
|
|
93
|
+
bloco = RangeBlock(
|
|
94
|
+
open=ultimo_preco_base,
|
|
95
|
+
close=novo_close,
|
|
96
|
+
direction=direcao
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
blocos.append(bloco)
|
|
100
|
+
ultimo_preco_base = novo_close
|
|
101
|
+
|
|
102
|
+
log.info(f"{len(blocos)} blocos de range gerados.")
|
|
103
|
+
return blocos
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
View responsável pela saída no terminal.
|
|
3
|
+
|
|
4
|
+
Layout linear, amigável para leitores de tela.
|
|
5
|
+
Sem uso de gráficos ASCII complexos.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List
|
|
9
|
+
from mtcli.logger import setup_logger
|
|
10
|
+
from .range_model import RangeBlock
|
|
11
|
+
|
|
12
|
+
log = setup_logger("mtcli.range.view")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RangeView:
|
|
16
|
+
"""
|
|
17
|
+
Responsável por exibir os blocos de forma textual estruturada.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def exibir_header(symbol: str, range_size: float, ancorado: bool):
|
|
22
|
+
"""Exibe cabeçalho descritivo."""
|
|
23
|
+
print("\n========================================")
|
|
24
|
+
print("GRÁFICO RANGE")
|
|
25
|
+
print("----------------------------------------")
|
|
26
|
+
print(f"Ativo: {symbol}")
|
|
27
|
+
print(f"Tamanho do Range: {range_size}")
|
|
28
|
+
print(f"Ancorado na abertura: {'SIM' if ancorado else 'NÃO'}")
|
|
29
|
+
print("========================================\n")
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def exibir_blocos(blocos: List[RangeBlock], numerar: bool):
|
|
33
|
+
"""
|
|
34
|
+
Exibe blocos linha a linha.
|
|
35
|
+
|
|
36
|
+
numerar:
|
|
37
|
+
- True -> exibe número da linha
|
|
38
|
+
- False -> não exibe numeração
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
if not blocos:
|
|
42
|
+
print("Nenhum bloco gerado.")
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
if numerar:
|
|
46
|
+
print("Número Direção Abertura Fechamento")
|
|
47
|
+
print("-----------------------------------")
|
|
48
|
+
else:
|
|
49
|
+
print("Direção Abertura Fechamento")
|
|
50
|
+
print("----------------------------")
|
|
51
|
+
|
|
52
|
+
for i, bloco in enumerate(blocos, start=1):
|
|
53
|
+
|
|
54
|
+
if numerar:
|
|
55
|
+
print(
|
|
56
|
+
f"{i:>6} "
|
|
57
|
+
f"{bloco.direction:<5} "
|
|
58
|
+
f"{bloco.open:>8.0f} "
|
|
59
|
+
f"{bloco.close:>10.0f}"
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
print(
|
|
63
|
+
f"{bloco.direction:<5} "
|
|
64
|
+
f"{bloco.open:>8.0f} "
|
|
65
|
+
f"{bloco.close:>10.0f}"
|
|
66
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mtcli-range"
|
|
3
|
+
version = "1.0.0.dev0"
|
|
4
|
+
description = "Range bars plugin para mtcli"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Valmir França", email = "vfranca3@gmail.com" }
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
license-files = ["LICENSE"]
|
|
11
|
+
requires-python = ">=3.10,<3.14"
|
|
12
|
+
|
|
13
|
+
keywords = [
|
|
14
|
+
"trading",
|
|
15
|
+
"range",
|
|
16
|
+
"metatrader5",
|
|
17
|
+
"mt5",
|
|
18
|
+
"cli",
|
|
19
|
+
"price-action",
|
|
20
|
+
"tape-reading"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Programming Language :: Python :: 3.13",
|
|
31
|
+
"Operating System :: OS Independent",
|
|
32
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
dependencies = [
|
|
36
|
+
"mtcli>=3.5.0.dev0,<3.6.0.dev0",
|
|
37
|
+
"click>=8.3.0,<9.0.0",
|
|
38
|
+
"metatrader5>=5.0.5370,<6.0.0"
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/vfranca/mtcli-range"
|
|
43
|
+
Repository = "https://github.com/vfranca/mtcli-range"
|
|
44
|
+
Issues = "https://github.com/vfranca/mtcli-range/issues"
|
|
45
|
+
Documentation = "https://vfranca.github.io/mtcli-range"
|
|
46
|
+
|
|
47
|
+
[project.entry-points."mtcli.plugins"]
|
|
48
|
+
range = "mtcli_range.plugin:register"
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
52
|
+
build-backend = "poetry.core.masonry.api"
|
|
53
|
+
|
|
54
|
+
[tool.poetry.group.dev.dependencies]
|
|
55
|
+
pytest = "^8.4.2"
|
|
56
|
+
pytest-cov = "^6.2.1"
|
|
57
|
+
pytest-mock = "^3.15.1"
|
|
58
|
+
ruff = "^0.14.1"
|
|
59
|
+
|
|
60
|
+
[tool.poetry.group.docs.dependencies]
|
|
61
|
+
mkdocs = "^1.6.1"
|
|
62
|
+
pymdown-extensions = "^10.16.1"
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 88
|
|
66
|
+
target-version = "py311"
|
|
67
|
+
|
|
68
|
+
[tool.ruff.lint]
|
|
69
|
+
select = ["E", "F", "B", "I", "UP", "N"]
|
|
70
|
+
ignore = ["E501"]
|
|
71
|
+
|
|
72
|
+
[tool.ruff.lint.isort]
|
|
73
|
+
known-first-party = ["mtcli", "mtcli_range"]
|
|
74
|
+
combine-as-imports = true
|
|
75
|
+
force-sort-within-sections = true
|
|
76
|
+
|
|
77
|
+
[tool.ruff.format]
|
|
78
|
+
quote-style = "double"
|
|
79
|
+
indent-style = "space"
|
|
80
|
+
line-ending = "auto"
|
|
81
|
+
skip-magic-trailing-comma = false
|