Using the library readline I managed to provide tab completion when using input(). My code until now looks like this:
import readline
class TabComplete:
def __init__(self, wordList):
self.wordList = wordList
def complete(self,text,state):
results = [x for x in self.wordList if x.startswith(text)] + [None]
return results[state]
readline.parse_and_bind("tab: complete")
tabComplete = ["IF", "IF_ELSE", "FOR", "WHILE"]
completer = TabComplete(tabComplete)
readline.set_completer(completer.complete)
userTyped = input("prompt > ")
If I start typing I and then hit twice on tab it will propose me IF and IF_ELSE as expected.
What I am searching now is:
if I start typing i and then hit twice tab is there a way to still make it propose IF and IF_ELSE ?
if I type IF EL can I tell him on real time to replace the space by _ so the completion works?
you could always convert your text to upper and replace spaces with "_"
def complete(self,text,state):
text = tex.upper().replace(" ","_")
results = [x for x in self.wordList if x.startswith(text)] + [None]
return results[state]
Related
I'm creating a command-line calculator tool, and I'd like to get it to format the user's input as they type, similar to what Fish and Powershell do (screenshots).
Currently, the only easy method for doing this that I can think of is getting characters one at a time, and then reprinting the formatted line to the screen.
# This code is probably very dodgy,
# it's just to give you an idea what I'm thinking
# Use external getch module to get input without echoing to screen
from getch import getch
inp = ""
while True:
char = getch()
if char == '\n': break
if char == '\b': inp = inp[:-1]
else: inp += char
# Print with carriage return so that next input overwrites it
print(colourFormatInput(inp) + '\r')
# Outside loop: process input and devliver results
Whilst this would techincally work, I feel like it is a ton of manual effort, and would only become more complex if I wanted to add functionality such as using arrow keys to move the cursor's position.
Is there a simple way to get this kind of functionality without having to code all of it up myself?
I use the library colorama.
import colorama
import sys
def hook(tp, *args):
if tp is KeyboardInterrupt:
print(colorama.Fore.RESET)
exit()
def colored_input(text: str, color):
sys.excepthook = hook
inp = input(text + color)
print(colorama.Fore.RESET, end="", flush=True)
sys.excepthook = sys.__excepthook__
return inp
name = colored_input("What's your name? ", colorama.Fore.RED)
age = colored_input("What's your age? ", colorama.Fore.YELLOW)
print(f"Nice to meet you {name}({age})")
I use sys.excepthook to catch the KeyboardInterrupt so I can reset the color back when the user types CTRL+C, and then I set the original excepthook back (sys.__excepthook__)
You can do this with prompt_toolkit
here is the documentation if you need:
Getting Started
Asking for input/prompts
You can add Syntax Highlighting to input by the following as given in examples:
Adding syntax highlighting is as simple as adding a lexer. All of the Pygments lexers can be used after wrapping them in a PygmentsLexer. It is also possible to create a custom lexer by implementing the Lexer abstract base class.
from pygments.lexers.html import HtmlLexer
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.lexers import PygmentsLexer
text = prompt('Enter HTML: ', lexer=PygmentsLexer(HtmlLexer))
print('You said: %s' % text)
In the same way as above you can create custom prompt_toolkit.lexers.Lexer for calculator highlighting just like the following example. Here I create a custom helper class:
from typing import Callable
from prompt_toolkit.document import Document
from prompt_toolkit.formatted_text.base import StyleAndTextTuples
from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.shortcuts import prompt
import prompt_toolkit.lexers
import re
class CustomRegexLexer(prompt_toolkit.lexers.Lexer):
def __init__(self, regex_mapping):
super().__init__()
self.regex_mapping = regex_mapping
def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]:
def lex(_: int):
line = document.text
tokens = []
while len(line) != 0:
for pattern, style_string in self.regex_mapping.items():
match: re.Match = pattern.search(line)
if not match:
continue
else:
# print(f"found_match: {match}")
pass
match_string = line[:match.span()[1]]
line = line[match.span()[1]:]
tokens.append((style_string, match_string))
break
return tokens
return lex
Now with the above helper class implemented we can create our regex patterns and their respective styles, to learn more about what you can have in styling string go to this page
# Making regex for different operators. Make sure you add `^` anchor
# to the start of all the patterns
operators_allowed = ["+", "-", "/", "*", "(", ")", "=", "^"]
operators = re.compile("^["+''.join([f"\\{x}" for x in operators_allowed])+"]")
numbers = re.compile(r"^\d+(\.\d+)?")
text = re.compile(r"^.")
regex_mapping = {
operators: "#ff70e5", # Change colors according to your requirement
numbers: "#ffa500",
text: "#2ef5ff",
}
MyCalculatorLexer = CustomRegexLexer(regex_mapping)
With the lexers created you can now use the lexer in the function prompt:
text = prompt("Enter Equation: ", lexer=MyCalculatorLexer)
# Or
def input_maths(message):
return prompt(message, lexer=MyCalculatorLexer)
text = input_maths("Enter Equation: ")
Here is some example output:
And now everything works. Also do check out prompt_toolkit, you can
create tons of custom things as shown in there gallery
Another example:
CGREEN = '\33[32m'
CYELLOW = '\33[33m'
CBLUE = '\33[34m'
CVIOLET = '\33[35m'
CBEIGE = '\33[36m'
CWHITE = '\33[37m'
CGREY = '\33[90m'
CRED = '\033[91m'
CYELLOW = '\33[33m'
CYELLOW2 = '\33[93m'
CEND = '\033[0m'
print(CGREEN + "This is green text" + CEND)
print(CYELLOW + "This is yellow text" + CEND)
# Another interesting example (courtesy: https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html)
for i in range(0, 16):
for j in range(0, 16):
code = str(i * 16 + j)
colorCode = u"\u001b[48;5;" + code + "m"
print(colorCode + " Color {} ".format(code) + CEND)
i give you an example i click on run write a number in the console(how many keys) and then i get this random letters and numbers: 61PYY-ZEPY1-2H82R-V1JZ1-9VEF7, but i want that they are in my demofile2.txt. when i paste this in the write: '-'.join(''.join(random.choice(seq) for _ in range(5)) for _ in range(5)) it says seq is not definied
This is the code :
import random, sys
class KeyGen():
def __init__(self):
global i
i = int(input("How many serial codes are you looking for? \n"))
print("")
self.main(i)
def main(self, count):
""" Our main iteration function, using simple
capital letters, along with the numbers 0-9 """
seq = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
for i in range(count):
print('-'.join(''.join(random.choice(seq) for _ in range(5)) for _ in range(5)))
print("\nCreated {} serial keys.".format(count))
if __name__ == '__main__':
app = KeyGen()
text_file = open("demofile2.txt.")
text_file.write(THISISWHATISEARCH)
text_file.close()
In order to write to a file you simply need to pass a string to file.write(). In your case the same string you're printing.
One difference is that print automatically adds a new line at the end of each string (because of the default value of the argument end='\n'). So when writing to the file you can add an ending new-line. Alternatively, you can use print's file argument to avoid any change at all!
Now you just need to have the file descriptor available at the point where you want to write to it. That might depend on the design of the rest of your code, for simplicity of the example I will simply move it to the main function.
One more thing is when read/write-ing files, it is better to use the with context manager:
import random, sys
class KeyGen():
def __init__(self):
global i
i = int(input("How many serial codes are you looking for? \n"))
print("")
self.main(i)
def main(self, count):
""" Our main iteration function, using simple
capital letters, along with the numbers 0-9 """
seq = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
with open("demofile2.txt.", 'w') as text_file:
for i in range(count):
print('-'.join(''.join(random.choice(seq) for _ in range(5)) for _ in range(5)), file=text_file)
# text_file.write('-'.join(''.join(random.choice(seq) for _ in range(5)) for _ in range(5)) + '\n')
print("\nCreated {} serial keys.".format(count), file=text_file)
# text_file.write("\nCreated {} serial keys.\n".format(count))
if __name__ == '__main__':
app = KeyGen()
I will use this chance for a general tip:
In Python there is no need to over-use classes. Maybe you come from a Java background where everything is classes, but here your class only serves as a gate-way to a function. So your code could be:
import random, sys
def main():
"""
Our main iteration function, using simple
capital letters, along with the numbers 0-9
"""
seq = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
count = int(input("How many serial codes are you looking for? \n"))
with open("demofile2.txt.", 'w') as text_file:
for _ in range(count):
print('-'.join(''.join(random.choice(seq) for _ in range(5)) for _ in range(5)), file=text_file)
# text_file.write('-'.join(''.join(random.choice(seq) for _ in range(5)) for _ in range(5)) + '\n')
print("\nCreated {} serial keys.".format(count), file=text_file)
# text_file.write("\nCreated {} serial keys.\n".format(count))
if __name__ == '__main__':
main()
Essentially I have a python script that loads in a number of files, each file contains a list and these are used to generate strings. For example: "Just been to see $film% in $location%, I'd highly recommend it!" I need to replace the $film% and $location% placeholders with a random element of the array of their respective imported lists.
I'm very new to Python but have picked up most of it quite easily but obviously in Python strings are immutable and so handling this sort of task is different compared to other languages I've used.
Here is the code as it stands, I've tried adding in a while loop but it would still only replace the first instance of a replaceable word and leave the rest.
#!/usr/bin/python
import random
def replaceWord(string):
#Find Variable Type
if "url" in string:
varType = "url"
elif "film" in string:
varType = "film"
elif "food" in string:
varType = "food"
elif "location" in string:
varType = "location"
elif "tvshow" in string:
varType = "tvshow"
#LoadVariableFile
fileToOpen = "/prototype/default_" + varType + "s.txt"
var_file = open(fileToOpen, "r")
var_array = var_file.read().split('\n')
#Get number of possible variables
numberOfVariables = len(var_array)
#ChooseRandomElement
randomElement = random.randrange(0,numberOfVariables)
#ReplaceWord
oldValue = "$" + varType + "%"
newString = string.replace(oldValue, var_array[randomElement], 1)
return newString
testString = "Just been to see $film% in $location%, I'd highly recommend it!"
Test = replaceWord(testString)
This would give the following output: Just been to see Harry Potter in $location%, I'd highly recommend it!
I have tried using while loops, counting the number of words to replace in the string etc. however it still only changes the first word. It also needs to be able to replace multiple instances of the same "variable" type in the same string, so if there are two occurrences of $film% in a string it should replace both with a random element from the loaded file.
The following program may be somewhat closer to what you are trying to accomplish. Please note that documentation has been included to help explain what is going on. The templates are a little different than yours but provide customization options.
#! /usr/bin/env python3
import random
PATH_TEMPLATE = './prototype/default_{}s.txt'
def main():
"""Demonstrate the StringReplacer class with a test sting."""
replacer = StringReplacer(PATH_TEMPLATE)
text = "Just been to see {film} in {location}, I'd highly recommend it!"
result = replacer.process(text)
print(result)
class StringReplacer:
"""StringReplacer(path_template) -> StringReplacer instance"""
def __init__(self, path_template):
"""Initialize the instance attribute of the class."""
self.path_template = path_template
self.cache = {}
def process(self, text):
"""Automatically discover text keys and replace them at random."""
keys = self.load_keys(text)
result = self.replace_keys(text, keys)
return result
def load_keys(self, text):
"""Discover what replacements can be made in a string."""
keys = {}
while True:
try:
text.format(**keys)
except KeyError as error:
key = error.args[0]
self.load_to_cache(key)
keys[key] = ''
else:
return keys
def load_to_cache(self, key):
"""Warm up the cache as needed in preparation for replacements."""
if key not in self.cache:
with open(self.path_template.format(key)) as file:
unique = set(filter(None, map(str.strip, file)))
self.cache[key] = tuple(unique)
def replace_keys(self, text, keys):
"""Build a dictionary of random replacements and run formatting."""
for key in keys:
keys[key] = random.choice(self.cache[key])
new_string = text.format(**keys)
return new_string
if __name__ == '__main__':
main()
The varType you are assigning will be set in only one of your if-elif-else sequence and then the interpreter will go outside. You would have to run all over it and perform operations. One way would be to set flags which part of sentence you want to change. It would go that way:
url_to_change = False
film_to_change = False
if "url" in string:
url_to_change = True
elif "film" in string:
film_to_change = True
if url_to_change:
change_url()
if film_to_change:
change_film()
If you want to change all occurances you could use a foreach loop. Just do something like this in the part you are swapping a word:
for word in sentence:
if word == 'url':
change_word()
Having said this, I'd reccomend introducing two improvements. Push changing into separate functions. It would be easier to manage your code.
For example function for getting items from file to random from could be
def load_variable_file(file_name)
fileToOpen = "/prototype/default_" + file_name + "s.txt"
var_file = open(fileToOpen, "r")
var_array = var_file.read().split('\n')
var_file.clos()
return var_array
Instead of
if "url" in string:
varType = "url"
you could do:
def change_url(sentence):
var_array = load_variable_file(url)
numberOfVariables = len(var_array)
randomElement = random.randrange(0,numberOfVariables)
oldValue = "$" + varType + "%"
return sentence.replace(oldValue, var_array[randomElement], 1)
if "url" in sentence:
setnence = change_url(sentence)
And so on. You could push some part of what I've put into change_url() into a separate function, since it would be used by all such functions (just like loading data from file). I deliberately do not change everything, I hope you get my point. As you see with functions with clear names you can write less code, split it into logical, reusable parts, no needs to comment the code.
A few points about your code:
You can replace the randrange with random.choice as you just
want to select an item from an array.
You can iterate over your types and do the replacement without
specifying a limit (the third parameter), then assign it to the same object, so you keep all your replacements.
readlines() do what you want for open, read from the file as store the lines as an array
Return the new string after go through all the possible replacements
Something like this:
#!/usr/bin/python
import random
def replaceWord(string):
#Find Variable Type
types = ("url", "film", "food", "location", "tvshow")
for t in types:
if "$" + t + "%" in string:
var_array = []
#LoadVariableFile
fileToOpen = "/prototype/default_" + varType + "s.txt"
with open(fname) as f:
var_array = f.readlines()
tag = "$" + t + "%"
while tag in string:
choice = random.choice(var_array)
string = string.replace(tag, choice, 1)
var_array.remove(choice)
return string
testString = "Just been to see $film% in $location%, I'd highly recommend it!"
new = replaceWord(testString)
print(new)
I have a Python application which outputs an SQL file:
sql_string = "('" + name + "', " + age + "'),"
output_files['sql'].write(os.linesep + sql_string)
output_files['sql'].flush()
This is not done in a for loop, it is written as data becomes available. Is there any way to 'backspace' over the last comma character when the application is done running, and to replace it with a semicolon? I'm sure that I could invent some workaround by outputting the comma before the newline, and using a global Bool to determine if any particular 'write' is the first write. However, I think that the application would be much cleaner if I could just 'backspace' over it. Of course, being Python maybe there is such an easier way!
Note that having each insert value line in a list and then imploding the list is not a viable solution in this use case.
Use seek to move your cursor one byte (character) backwards, then write the new character:
f.seek(-1, os.SEEK_CUR)
f.write(";")
This is the easiest change, maintaining your current code ("working code" beats "ideal code") but it would be better to avoid the situation.
How about adding the commas before adding the new line?
first_line = True
...
sql_string = "('" + name + "', " + age + "')"
if not first_line:
output_files['sql'].write(",")
first_line = False
output_files['sql'].write(os.linesep + sql_string)
output_files['sql'].flush()
...
output_files['sql'].write(";")
output_files['sql'].flush()
You did mention this in your question - I think this is a much clearer to a maintainer than seeking commas and overwriting them.
EDIT: Since the above solution would require a global boolean in your code (which is not desirable) you could instead wrap the file writing behaviour into a helper class:
class SqlFileWriter:
first_line = True
def __init__(self, file_name):
self.f = open(file_name)
def write(self, sql_string):
if not self.first_line:
self.f.write(",")
self.first_line = False
self.f.write(os.linesep + sql_string)
self.f.flush()
def close(self):
self.f.write(";")
self.f.close()
output_files['sql'] = SqlFileWriter("myfile.sql")
output_files['sql'].write("('" + name + "', '" + age + "')")
This encapsulates all the SQL notation logic into a single class, keeping the code readable and at the same time simplifying the caller code.
Try opening the file to write as binary: 'wb' instead of 'w'.
Use generators, e.g.:
def with_separator(data, sep):
first = True:
for datum in data:
if first:
first = False
else:
yield sep
yield datum
with open("sdfasdfas", "w") as outf:
for x in with_separator(sql_get_rows(), ",\n"):
outf.write(x)
# flush if needed
For hardcore iterator use, this should get you started:
In [11]: list( itertools.imap("".join, itertools.izip(itertools.chain([""], itertools.repeat(",\n")), "abc")) )
Out[11]: ['a', ',\nb', ',\nc']
If your data uses imperative API, that is not iterable, send() your data to generator:
def write_with_separator(filename, sep):
with file(filename, "w"):
first = True
yield None
while True:
datum = yield None
if first:
first = False
else:
fout.write(sep)
fout.write(datum)
# flush if needed
writer = write_with_separator("somefilename", ",\n")
writer.next() # can't send to just-started generator
# to be called when you get data
for row in sql_get_rows():
writer.send(row)
I'm using Zelle Graphics library and I'm having trouble replacing graphics objects (which, in this case, happens to be text objects).
Here's the code:
from Graphics import *
winName = "Window"
win = Window(winName,600,500)
win.setBackground(Color('silver'))
title = Text((300,20),"Zack's Flash Card Maker")
title.draw(win)
p1 = Rectangle((50, 100),(550,400))
p1.setFill(Color("black"))
p1.draw(win)
class FlashCard:
def __init__(self):
self.commands = {'addQuestion':self.addQuestion,'startGame':self.startGame}
self.stack = []
self.questions = {}
self.questionAnswered = False
self.questionsCorrect = 0
self.questionsIncorrect = 0
def addQuestion(self):
question = ' '.join(self.stack)
self.stack = []
answer = input(question)
self.questions[question] = answer
def startGame(self):
for question in self.questions:
if(self.questionAnswered == False):
answer=input(question)
questionText = Text((300,150),question)
questionText.setFill(Color("white"))
questionText.draw(win)
if(answer == self.questions[question]):
questionAnswer = Text((300,200),answer + " is correct!")
questionAnswer.setFill(Color("green"))
questionAnswer.draw(win)
self.questionsCorrect = self.questionsCorrect + 1
continue
else:
questionAnswer = Text((300,200),answer + " is incorrect. Study this one.")
questionAnswer.setFill(Color("red"))
questionAnswer.draw(win)
self.questionsIncorrect = self.questionsIncorrect + 1
continue
def interpret(self,expression):
for token in expression.split():
if token in self.commands:
operator = self.commands[token]
operator()
else:
self.stack.append(token)
i = FlashCard()
i.interpret('What is your dog\'s name? addQuestion')
i.interpret('What is your favorite thing to do? addQuestion')
i.interpret('startGame')
This is essentially a mini flash card program I'm making. It takes the interpret commands at the bottom and executes them based on the dictionary in the FlashCard class. It basically works: it does the correct text objects. However, text begins to overlap other text objects because it re-draws. I've tried moving the .draw function all over, but it either doesn't appear at all or it overlaps.
Anyone have any suggestions? I want the text to replace for each new flashcard question.
Thanks!
there's an undraw() command that you need to use if you want to make something invisible. I'd recommend placing it right before your continue statements. It's used like
questionText.undraw()
questionAnswer.undraw()
Alternatively, you can use the del command to get rid of each questionText/questionAnswer instance when you're done with it. That's probably a better option since you're actually freeing up the memory that way instead of storing data and not doing anything with it.
You can use setText method to change the text.
example:
string = Text(Point(1, 1), 'original string')
sting.setText('new string')