Menu CLI - Python

Github - Cli-menu

CLI - Menu

Simples menu para utilizar em suas aplicações CLI. Código simples e comentado, para futuras atualizações

Como utilizar:

Instale a biblioteca pyfiglet

python -m pip install pyfiglet

Clone o repositório do git, acesse a pasta src (pasta que contém os arquivos .py), execute o arquivo principal main.py

git clone https://github.com/fellipematos/simple-menu-cli
cd cli-menu/src
python main.py

Arquivo de Configuração menuConfig.py

title: Define o titulo da sua aplicação.

txtMenuBreak: Define o texto para menu sair.

msgBreak: Define a mensagem para finalizar sua aplicação.

msgInvalidOption: Define a mensagem para opções inválida.

Arquivo de Funções actions.py

Arquivo para criar suas funções do menu.

Você pode utilizar apenas o arquivo menuConfig.py nas suas aplicações. Importe o arquivo na sua aplicação, define a variável com as opções do menu conforme o modelo abaixo

from menuConfig import *

START = [
  ("Nome da Opcao 1", funcaoMenu1),
  ("Nome da Opcao 2", funcaoMenu2)
]

Depois que definir as opções do menu, inicie a função Menu() com os parâmetros *conforme o modelo abaixo

menu("", START)

[Github - Cli-menu](https://github.com/fellipematos/cli-menu

Só um detalhe. No seu código vc usa isdigit para verificar se foi digitado um número, e depois converte usando int:

if menuOption.isdigit():
    if int(menuOption) == 0:
        etc

O que serve para a maioria dos casos. Mas como o usuário pode digitar qualquer coisa, então existem situações que podem dar problema. Pois para determinados caracteres, isdigit() retorna True, mas dá erro ao converter para int. Fiz um programa para demonstrar isso:

from unicodedata import name

for i in range(0x10ffff + 1):
    s = chr(i)
    if s.isdigit():
        try:
            int(s)
        except ValueError:
            print(f'{i:06X} - {s} - {name(s, "")}')

Basicamente, o programa faz um loop por todos os caracteres Unicode (0x10ffff é valor máximo suportado), e caso isdigit() retorne True, tenta converter para int. Se der erro na conversão, imprime os dados do caractere.

Testei com Python 3.11.4, que segundo a documentação, usa a versão 14 do Unicode. Diferentes versões do Python podem usar outras versões do Unicode, então a lista abaixo pode não ser exatamente igual, mas enfim, usando esta versão foram encontrados 128 caracteres:

0000B2 - ² - SUPERSCRIPT TWO
0000B3 - ³ - SUPERSCRIPT THREE
0000B9 - ¹ - SUPERSCRIPT ONE
001369 - ፩ - ETHIOPIC DIGIT ONE
00136A - ፪ - ETHIOPIC DIGIT TWO
00136B - ፫ - ETHIOPIC DIGIT THREE
00136C - ፬ - ETHIOPIC DIGIT FOUR
00136D - ፭ - ETHIOPIC DIGIT FIVE
00136E - ፮ - ETHIOPIC DIGIT SIX
00136F - ፯ - ETHIOPIC DIGIT SEVEN
001370 - ፰ - ETHIOPIC DIGIT EIGHT
001371 - ፱ - ETHIOPIC DIGIT NINE
0019DA - ᧚ - NEW TAI LUE THAM DIGIT ONE
002070 - ⁰ - SUPERSCRIPT ZERO
002074 - ⁴ - SUPERSCRIPT FOUR
002075 - ⁵ - SUPERSCRIPT FIVE
002076 - ⁶ - SUPERSCRIPT SIX
002077 - ⁷ - SUPERSCRIPT SEVEN
002078 - ⁸ - SUPERSCRIPT EIGHT
002079 - ⁹ - SUPERSCRIPT NINE
002080 - ₀ - SUBSCRIPT ZERO
002081 - ₁ - SUBSCRIPT ONE
002082 - ₂ - SUBSCRIPT TWO
002083 - ₃ - SUBSCRIPT THREE
002084 - ₄ - SUBSCRIPT FOUR
002085 - ₅ - SUBSCRIPT FIVE
002086 - ₆ - SUBSCRIPT SIX
002087 - ₇ - SUBSCRIPT SEVEN
002088 - ₈ - SUBSCRIPT EIGHT
002089 - ₉ - SUBSCRIPT NINE
002460 - ① - CIRCLED DIGIT ONE
002461 - ② - CIRCLED DIGIT TWO
002462 - ③ - CIRCLED DIGIT THREE
002463 - ④ - CIRCLED DIGIT FOUR
002464 - ⑤ - CIRCLED DIGIT FIVE
002465 - ⑥ - CIRCLED DIGIT SIX
002466 - ⑦ - CIRCLED DIGIT SEVEN
002467 - ⑧ - CIRCLED DIGIT EIGHT
002468 - ⑨ - CIRCLED DIGIT NINE
002474 - ⑴ - PARENTHESIZED DIGIT ONE
002475 - ⑵ - PARENTHESIZED DIGIT TWO
002476 - ⑶ - PARENTHESIZED DIGIT THREE
002477 - ⑷ - PARENTHESIZED DIGIT FOUR
002478 - ⑸ - PARENTHESIZED DIGIT FIVE
002479 - ⑹ - PARENTHESIZED DIGIT SIX
00247A - ⑺ - PARENTHESIZED DIGIT SEVEN
00247B - ⑻ - PARENTHESIZED DIGIT EIGHT
00247C - ⑼ - PARENTHESIZED DIGIT NINE
002488 - ⒈ - DIGIT ONE FULL STOP
002489 - ⒉ - DIGIT TWO FULL STOP
00248A - ⒊ - DIGIT THREE FULL STOP
00248B - ⒋ - DIGIT FOUR FULL STOP
00248C - ⒌ - DIGIT FIVE FULL STOP
00248D - ⒍ - DIGIT SIX FULL STOP
00248E - ⒎ - DIGIT SEVEN FULL STOP
00248F - ⒏ - DIGIT EIGHT FULL STOP
002490 - ⒐ - DIGIT NINE FULL STOP
0024EA - ⓪ - CIRCLED DIGIT ZERO
0024F5 - ⓵ - DOUBLE CIRCLED DIGIT ONE
0024F6 - ⓶ - DOUBLE CIRCLED DIGIT TWO
0024F7 - ⓷ - DOUBLE CIRCLED DIGIT THREE
0024F8 - ⓸ - DOUBLE CIRCLED DIGIT FOUR
0024F9 - ⓹ - DOUBLE CIRCLED DIGIT FIVE
0024FA - ⓺ - DOUBLE CIRCLED DIGIT SIX
0024FB - ⓻ - DOUBLE CIRCLED DIGIT SEVEN
0024FC - ⓼ - DOUBLE CIRCLED DIGIT EIGHT
0024FD - ⓽ - DOUBLE CIRCLED DIGIT NINE
0024FF - ⓿ - NEGATIVE CIRCLED DIGIT ZERO
002776 - ❶ - DINGBAT NEGATIVE CIRCLED DIGIT ONE
002777 - ❷ - DINGBAT NEGATIVE CIRCLED DIGIT TWO
002778 - ❸ - DINGBAT NEGATIVE CIRCLED DIGIT THREE
002779 - ❹ - DINGBAT NEGATIVE CIRCLED DIGIT FOUR
00277A - ❺ - DINGBAT NEGATIVE CIRCLED DIGIT FIVE
00277B - ❻ - DINGBAT NEGATIVE CIRCLED DIGIT SIX
00277C - ❼ - DINGBAT NEGATIVE CIRCLED DIGIT SEVEN
00277D - ❽ - DINGBAT NEGATIVE CIRCLED DIGIT EIGHT
00277E - ❾ - DINGBAT NEGATIVE CIRCLED DIGIT NINE
002780 - ➀ - DINGBAT CIRCLED SANS-SERIF DIGIT ONE
002781 - ➁ - DINGBAT CIRCLED SANS-SERIF DIGIT TWO
002782 - ➂ - DINGBAT CIRCLED SANS-SERIF DIGIT THREE
002783 - ➃ - DINGBAT CIRCLED SANS-SERIF DIGIT FOUR
002784 - ➄ - DINGBAT CIRCLED SANS-SERIF DIGIT FIVE
002785 - ➅ - DINGBAT CIRCLED SANS-SERIF DIGIT SIX
002786 - ➆ - DINGBAT CIRCLED SANS-SERIF DIGIT SEVEN
002787 - ➇ - DINGBAT CIRCLED SANS-SERIF DIGIT EIGHT
002788 - ➈ - DINGBAT CIRCLED SANS-SERIF DIGIT NINE
00278A - ➊ - DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ONE
00278B - ➋ - DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT TWO
00278C - ➌ - DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT THREE
00278D - ➍ - DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FOUR
00278E - ➎ - DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FIVE
00278F - ➏ - DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SIX
002790 - ➐ - DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN
002791 - ➑ - DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT EIGHT
002792 - ➒ - DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT NINE
010A40 - 𐩀 - KHAROSHTHI DIGIT ONE
010A41 - 𐩁 - KHAROSHTHI DIGIT TWO
010A42 - 𐩂 - KHAROSHTHI DIGIT THREE
010A43 - 𐩃 - KHAROSHTHI DIGIT FOUR
010E60 - 𐹠 - RUMI DIGIT ONE
010E61 - 𐹡 - RUMI DIGIT TWO
010E62 - 𐹢 - RUMI DIGIT THREE
010E63 - 𐹣 - RUMI DIGIT FOUR
010E64 - 𐹤 - RUMI DIGIT FIVE
010E65 - 𐹥 - RUMI DIGIT SIX
010E66 - 𐹦 - RUMI DIGIT SEVEN
010E67 - 𐹧 - RUMI DIGIT EIGHT
010E68 - 𐹨 - RUMI DIGIT NINE
011052 - 𑁒 - BRAHMI NUMBER ONE
011053 - 𑁓 - BRAHMI NUMBER TWO
011054 - 𑁔 - BRAHMI NUMBER THREE
011055 - 𑁕 - BRAHMI NUMBER FOUR
011056 - 𑁖 - BRAHMI NUMBER FIVE
011057 - 𑁗 - BRAHMI NUMBER SIX
011058 - 𑁘 - BRAHMI NUMBER SEVEN
011059 - 𑁙 - BRAHMI NUMBER EIGHT
01105A - 𑁚 - BRAHMI NUMBER NINE
01F100 - 🄀 - DIGIT ZERO FULL STOP
01F101 - 🄁 - DIGIT ZERO COMMA
01F102 - 🄂 - DIGIT ONE COMMA
01F103 - 🄃 - DIGIT TWO COMMA
01F104 - 🄄 - DIGIT THREE COMMA
01F105 - 🄅 - DIGIT FOUR COMMA
01F106 - 🄆 - DIGIT FIVE COMMA
01F107 - 🄇 - DIGIT SIX COMMA
01F108 - 🄈 - DIGIT SEVEN COMMA
01F109 - 🄉 - DIGIT EIGHT COMMA
01F10A - 🄊 - DIGIT NINE COMMA

Alguns foram mostrados como ? por não serem suportados no meu terminal.


Claro que na grande maioria dos casos será pouco provável que o usuário digite algum dos caracteres acima. Mas se qualquer coisa pode ser digitada (pois input() aceita qualquer caractere Unicode), então o programa deveria estar preparado para receber qualquer coisa.

Neste caso, o mais garantido é simplesmente tentar converter direto para int e capturar o erro:

try:
    # já tenta converter para int
    menuOption = int(input(">>> "))

    # se chegou aqui, é porque a conversão deu certo, então prossegue
    if menuOption == 0:
        print(msgBreak) #mensagem para finalizar
        break

    if menuOption < len(options) + 1:
        action = options[menuOption - 1][1]()
        print(action)
except ValueError: # se deu erro, mostra mensagem
    print(msgInvalidOption)

Outra vantagem é que agora eu só faço a conversão para int apenas uma vez, logo após a leitura (no seu código vc faz int(menuOption) várias vezes, sendo que poderia fazer apenas uma vez antes de começar a usar o valor).


Outra solução é usar isdecimal() no lugar de isdigit(), já que todos os caracteres para os quais isdecimal() retorna True podem ser convertidos para int. Mas eu ainda faria a conversão para int apenas uma vez:

menuOption = input(">>> ")
if menuOption.isdecimal():
    menuOption = int(menuOption) # converte para int apenas uma vez

    if menuOption == 0:
        print(msgBreak) #mensagem para finalizar
        break

    if menuOption < len(options) + 1:
        action = options[menuOption - 1][1]()
        print(action)
else:
    print(msgInvalidOption)

Para entender melhor essa história de isdigit e int nem sempre estarem "sincronizados", leia aqui.

O fato de ser pouco provável é o maior problema. Porque passa a ideia que está certo, quando apenas "funciona". E funciona porque alguém não digitou o que dá problema. E o teste básico não pega o erro, e faz a pessoas terem a crença que está bom. ![Fiat 147 todo detonado andando pelas ruas](https://i.stack.imgur.com/zdAbK.jpg) --- Farei algo que muitos pedem para aprender a programar corretamente, **gratuitamente**. Para saber quando, me segue nas suas plataformas preferidas. Quase não as uso, não terá infindas notificações ([links aqui](https://github.com/maniero/SOpt)).
Obrigado pela explicação @kht, vou tentar melhora essa parte. Entendi perfeitamente, não tinha esse conhecimento. Muito obrigado 🤓👍🏼

Dahora pra caramba. Vou ver se faço um fork dele. Vou contribuir!!

(Edit) Se quiser falar comigo pessoalmente manda um friend request no Discord pro username marcdev_ (já que agora no Discord nao tem mais aquele negocio de seu-nome#0000) ou no twitter @marc-dantas

Ja fiz o pull request pro teu repositório.
opa legal, obrigado vou dar um olhada. 🤓👍🏼

Um CUI bem completo também é o textual.

Alternate Text