How is current file location calculated in vim? (value of expand('%:p')) - python

While using the (excellent) Python autocompletion of YouCompleteMe (Jedi apparently) sometimes I'm suddenly unable to save because what vim thinks the path to my current file changes. When I open a file with vim bpython/curtsiesfrontend/repl.py, running
:echo expand('%:p')
gives me
/Users/tomb/Dropbox/code/bpython/bpython/curtsiesfrontend/repl.py
but after I use the completion, the same command gives
bpython/curtsiesfrontend/repl.py
and I can no longer save the file because that's not a path that exists. :pwd gives /Users/tomb/Dropbox/code/bpython before and after - so there must be some other part to the equation "what is the full directory path to this file."
I'm wondering what vim commands or concepts I should be looking at to identify the issue. There must be some concept of current directory of a file that is changing (however vim calculates the value of expand('%:p')) but I don't know what to call it. What is the concept of current directory that is changing?

The current file location is relative to Vim current path, which can be set with :cd or :lcd.
In order to identify where it is being changed you could use the 'verbose' option along the :redir command:
redir #a
set verbose=9
<execute the steps to reproduce the issue>
redir end
set verbose&
new
put a
Then search for 'chdir'. Increasing the value of 'verbose' displays more information, but it also make it harder to execute each step to reproduce the issue. After identifying a smaller test procedure you could repeat these steps using a higher value for 'verbose'.

Related

Python find broken symlinks caused by Git

I'm currently working on a script that is supposed to go through a cloned Git repo and remove any existing symlinks (broken or not) before recreating them for the user to ease with project initialization.
The solved problems:
Finding symbolic links (broken or unbroken) in Python is very well documented.
GitHub/GitLab breaking symlinks upon downloading/cloning/updating repositories is also very well documented as is how to fix this problem. Tl;dr: Git will download symlinks within the repo as plain text files (with no extension) containing only the symlink path if certain config flags are not set properly.
The unsolved problem:
My problem is that developers may download this repo without realizing the issues with Git, and end up with the symbolic links "checked out as small plain files that contain the link text" which is completely undetectable (as far as I can tell) when parsing the cloned files/directories (via existing base libraries). Running os.stat() on one of these broken symlink files returns information as though it were a normal text file:
os.stat_result(st_mode=33206, st_ino=14073748835637675, st_dev=2149440935, st_nlink=1, st_uid=0, st_gid=0, st_size=42, st_atime=1671662717, st_mtime=1671662699, st_ctime=1671662699)
The st_mode information only indicates that it is a normal text file- 100666 (the first 3 digits are the file type and the last 3 are the UNIX-style permissions). It should show up as 120000.
The os.path.islink() function only ever returns False.
THE CONFUSING PART is that when I run git ls-files -s ./microservices/service/symlink_file it gives 1200000 as the mode bits which, according to the documentation, indicates that this file is a symlink. However I cannot figure out how to see this information from within Python.
I've tried a bunch of things to try and find and delete existing symlinks. Here's the base method that just finds symlink directories and then deletes them:
def clearsymlinks(cwd: str = ""):
"""
Crawls starting from root directory and deletes all symlinks
within the directory tree.
"""
if not cwd:
cwd = os.getcwd()
print(f"Clearing symlinks - starting from {cwd}")
# Create a queue
cwd_dirs: list[str] = [cwd]
while len(cwd_dirs) > 0:
processing_dir: str = cwd_dirs.pop()
# print(f"Processing {processing_dir}") # Only when debugging - else it's too much output spam
for child_dir in os.listdir(processing_dir):
child_dir_path = os.path.join(processing_dir, child_dir)
# check if current item is a directory
if not os.path.isdir(child_dir_path):
if os.path.islink(child_dir_path):
print(f"-- Found symbolic link file {child_dir_path} - removing.\n")
os.remove(child_dir_path)
# skip the dir checking
continue
# Check if the child dir is a symlink
if os.path.islink(child_dir_path):
print(f"-- Found symlink directory {child_dir_path} - removing.")
os.remove(child_dir_path)
else:
# Add the child dir to the queue
cwd_dirs.append(child_dir_path)
After deleting symlinks I run os.symlink(symlink_src, symlink_dst) and generally run into the following error:
Traceback (most recent call last):
File "C:\Users\me\my_repo\remakesymlinks.py", line 123, in main
os.symlink(symlink_src, symlink_dst)
FileExistsError: [WinError 183] Cannot create a file when that file already exists: 'C:\\Users\\me\\my_repo\\SharedPy' -> 'C:\\Users\\me\\my_repo\\microservices\\service\\symlink_file'
A workaround to specifically this error (inside the create symlink method) is:
try:
os.symlink(symlink_src, symlink_dst)
except FileExistsError:
os.remove(symlink_dst)
os.symlink(symlink_src, symlink_dst)
But this is not ideal because it doesn't prevent a huge list of defunct/broken symlinks from piling up in the directory. I should be able to find any symlinks (working, broken, non-existent, etc.) and then delete them.
I have a list of the symlinks that should be created by my script, but extracting the list of targets from this list is also a workaround that also causes a 'symlink-leak'. Below is how I'm currently finding the broken symlink purely for testing purposes.
if not os.path.isdir(child_dir_path):
if os.path.basename(child_dir_path) in [s.symlink_install_to for s in dirs_to_process]:
print(f"-- Found symlink file {child_dir_path} - removing.")
os.remove(child_dir_path)
# skip the dir checking
continue
A rudimentary solution where I filter for only 'text/plain' files with exactly 1 line (since checking anything else is pointless) and trying to determine whether that single line is just a file path (this seems excessive though):
# Check if Git downloaded the symlink as a plain text file (undetectable broken symlink)
if not os.path.isdir(child_dir_path):
try:
if magic.Magic(mime = True, uncompress = True).from_file(child_dir_path) == 'text/plain':
with open(child_dir_path, 'r') as file:
for i, line in enumerate(file):
if i >= 1:
raise StopIteration
else:
# this function is directly copied from https://stackoverflow.com/a/34102855/8705841
if is_path_exists_or_creatable(line):
print(f"-- Found broken Git link file '{child_dir_path}' - removing.")
print(f"\tContents: \"{line}\"")
# os.remove(child_dir_path)
raise StopIteration
except (StopIteration, UnicodeDecodeError, magic.MagicException):
file.close()
continue
Clearly this solution would require a lot of refactoring (9 indents is pretty ridiculous) if it's the only viable option. Problem with this solution (currently) is that it also tries to delete any single-line files with a string that does not break pathname requirements- i.e. import _virtualenv, some random test string, project-name. Filtering out those files with spaces, or files without slashes, could potentially work but this still feels like chasing edge cases instead of solving the actual problem.
I could potentially rewrite this script in Bash wherein I could, in addition to existing symlink search/destroy code, parse the results from git ls-files -s ... and then delete any files with the 120000 tag. But is this method feasible and/or reliable? There should be a way to do this from within Python since Bash isn't going to run on every OS.
Note: file names have been redacted/changed after copy-paste for privacy, they shouldn't matter anyways since they are generated dynamically by the path searching functions

Why does a python program continue to write to the directory in which it was originally located, after I move the file to another directory?

I have 2 directories. I had a python program located in dir_1 writing to a .txt file also in dir_1. I meant to create them in dir_2, but when I move them both to dir_2, the python program, instead of writing to the existing .txt file that is now with it in dir_2, creates a new .txt file in dir_1 and writes to it. How do I fix this? I'm very new to programming and python and googling didn't help me out, probably because I didn't know what exactly to search.
with open('guest_book.txt', 'w') as file:
while True:
name = input('Please enter your name: ')
if name == 'q':
break
else:
print(f"Hello, {name.title()}!\nYou have been added to the guest"
f"book")
file.write(f"{name.title()}\n")
Python writes to the file location you supply it with. If this file location is a relative path, then it will create files relative to the directory of the script. I.e. when you move the script then the .txt file will be created relative to the new directiory.
On the other hand, if you provide an absolute path, then it does not matter where the script is located / where you execute it from. Instead, it will create the file at that location always.
From the sounds of it, you are using an absolute path when you want a relative path.
So change from something like /home/bob/file.txt (Linux) or C:\\Users\Bob\file.txt (Win) to simply file.txt or even ./file.txt.
Update: Since you were using a relative location all along, the problem will lie with the context that you are executing the script from. Your code is not the issue here, it is how you are executing it.
As vlovero suggests, maybe your IDE is not executing the new file in its new location?
One way you can test this robustly is to navigate to dir_2 in a terminal and run
python your_program_name.py
This will execute the script in the dir_2 location.
Since you have not specified an absolute path, your program is then specifying a directory relative to the current working directory (if instead, for example, you had specified a path such as '../guest_book.txt', you would have been specifying a directory one level above the current working directory). So let's imagine your OS is Linux and the Python program resides in /my_home/programs:
cd /my_home/data # this is the current working directory
python ../programs/your_program.py
The current working directory when the program is executed is /home/my_home/data even though the program being executed resides in /my_home/programs, and thus the output file will be created in the /my_home/data directory. os.getcwd() can be called to tell you what the current working directory is.

FTP giving 550 error when using ftp.cwd() twice

When I run this part:
directory = ftp.pwd()
file_name = 'config.single'
ftp.cwd('plugins/GAListener')
print('dir:', directory)
ftp.cwd('plugins/CrateReloaded/crates')
It says:
ftplib.error_perm: 550 No such file or directory.
When I change the directory several times inside an if statement, it works fine.
Are you unable to change the working directory like that? Do I need to reset to the main server directory prior to changing to another? If so, how would I do that?
Whichever directory comes first in the code is the one it goes to, then gives an error upon trying to change to the second. With the second ftp.cwd() commented out, the first one runs without problem, no matter which directory it points to, so they're definitely both there.
Additionally, when I print directory, it just prints / and not plugins/GAListener.
Edit: When doing this inside an if statement, all the directories go where they're supposed to and I'm given no errors without a leading slash.
if day_of_week == 0 and file_name not in ftp.nlst():
ftp.rename('config.yml', 'config.single')
ftp.rename('config.double', 'config.yml')
print('plugins/GAListener/config.yml is now plugins/GAListener/config.single.')
print('plugins/GAListener/config.double is now plugins/GAListener/config.yml.')
ftp.cwd('plugins/MOTDCountdown')
ftp.rename('config.yml', 'config.sunday')
ftp.rename('config.monday', 'config.yml')
print('plugins/MOTDCountdown/config.yml is now plugins/MOTDCountdown/config.sunday.')
print('plugins/MOTDCountdown/config.monday is now plugins/MOTDCountdown/config.yml.')
ftp.cwd('plugins/Essentials')
ftp.rename('motd.txt', 'motd.sunday')
ftp.rename('motd.monday', 'motd.txt')
After the first cwd you end up in folder:
/plugins/GAListener
Changing to a relative path plugins/CrateReloaded/crates (without a leading slash) will resolve it against the current working directory. So it will attempt to open folder:
/plugins/GAListener/plugins/CrateReloaded/crates
Which most probably does not exist.
I assume you want to go to
/plugins/CrateReloaded/crates
For that you have to use an absolute path (with a leading slash):
ftp.cwd('/plugins/CrateReloaded/crates')

vscode - read file from current folder where .py file is

I'm very new to programming, and to vscode.
I'm learning Python and currently I am learning about working with files.
The path looks like this: /home/anewuser/learning/chapter10.
The problem: completely basic "read file in python" lesson does not work in vscode because no such file or directory error raises when running my .py file, located in ~/learning/chapter10. But vscode wants that my .txt file I am supposed to open in python, to be in ~/learning directory, then it works. I don't like this behaviour.
All I want is to be able to read file placed in the directory where the .py file is. How to do this?
Because in your case ~/learning is the default cwd (current working directory), VSCode looks for pi_digits.txt in that location. If you put pi_digits.txt beside file_reader.py (which is located at ~/learning/chapter10), you'll have to specify the path (by prepending chapter10/ to the .txt file).
So you should do this:
with open('chapter10/pi_digits.txt') as file_object:
contents = file_object.read()
print(contents)
If you want to change the default current working directory (for example you want to change it to ~/learning/chapter10), you'll have to do the following:
~/learning/chapter10/file_reader.py
import os # first you need to import the module 'os'
# set the cwd to 'chapter10'
os.chdir('chapter10')
# now 'file_reader.py' and 'pi_digits.txt' are both in the cwd
with open('pi_digits.txt') as file_object:
contents = file_object.read()
print(contents)
With os.chdir('chapter10') you've set chapter10 as the default cwd, in which VSCode now will look for pi_digits.txt.
For detailed information about os.chdir() you can read through the official documentation or take a look at this post on stackoverflow.
In "User Settings", use the search bar to look for "python.terminal.executeInFileDir" and set (=) its value to "true" instead of "false".
I took this answer from here
How to run python interactive in current file's directory in Visual Studio Code?
this is my first time putting an answer on StackOverflow
so I apologize if I didn't do it the right way

Python - extract and modify a file path in all files in a directory in linux

I have files .sh files and .json files in which there are file paths given to point to a specific directory, but I should keep on changing the file path, depending on where my python scipt is run.
eg:content of one of my .sh file is
"cd /home/aswany/BotStudioInstallation/databricks/platform/databricksastro"
and I should change the file path via python code where the following path
"/home/aswany/BotStudioInstallation/" keep on changing depending on where databicks is located,
I tried the following code:
replaceAll(str(self.currentdirectory)+
"/databricks/platform/devsettings.json",
"/home/holmes/BotStudioInstallation",self.currentdirectory)
and function replaceAll is:
def replaceAll(self,file,searchExp,replaceExp):
for line in fileinput.input(file, inplace=1):
if searchExp in line:
line = line.replace(searchExp,replaceExp)
sys.stdout.write(line)
but above code only replaces a line
"home/holmes/BotStudioInstallation" to the current directory I am logged in,bt it cannot be sure that "home/holmes/BotStudioInstallation" is the only possibility it keep on changing like "home/aswany/BotStudioInstallation","home/dev3/BotStudioInstallation" etc ,I thought of regular expression for this.
please help me
Not sure I 100% understood your issue, but maybe I can help nonetheless.
As pointed out by J.F. Sebastian, you can use relative paths and remove the base part of the path. Using ./databricks/platform/devsettings.json might be enough. This is by far the most elegant solution.
If for any reason it is not, you can keep the directory you need to access, then append it to the base directory whenever you need it. That should allow you to deal with changes in the base directory. Though in the case the files will be used by other applications than your own, that might not be an option.
dir = get_dir_from_json()
dir_with_base = self.currentdirectory + dir
Alternatively, not an elegant solution though, without using regex you can use a "pattern" to always replace.
{
"directory": "<<_replace_me_>>/databricks/platform"
}
Then you know you can always replace "<<_replace_me_>>" with the base directory.

Categories

Resources