P1_ProjectLauncher
Le 六 07 三月 2026
本篇为个人工具项目ProjectLauncher的介绍,如果感兴趣,可以直接复制代码,整个项目总共为两个文件,并且只是为了文件看起来好看而分开,实际可以放置于统一文件中。
ProjectLauncher,从名字就可以看出来,用于启动项目,存在的原因很简单,我是一个以终端作为中心的工作流,而这是我的一个项目的路径:/run/media/qingyu/Making/Arukas_Java/src/main/java/com/qingyu。并且为了调试方便,我会开两个终端,运行位于Arukas_Java中的Gradle脚本。而这个时候就很难受了,需要cd进这两个目录,然后再启动Nvim或者输入运行指令。像这样的目录有很多,而且我是个相当三心二意的人,我会同时开非常多的大小项目,于是,我就随便写了这个ProjectLauncher,用于比较统一的管理项目,以及对开始工作前的初始化工作进行自动化。
这个项目的需求就是,以终端为核心(如果你习惯使用图形界面,这个项目对你来说没那么有用),自动执行,允许多窗口,允许自定义。所以我开发了一个非常简单的流程控制脚本用作配置。
大致的使用方法如下:
pl init <ProjectName> --初始化当前目录为工作目录
nvim INIT.plc --进行流程文件的编写,我使用的是nvim,你可以随便用别的编辑器
pl run <ProjectName> --运行名称为<ProjectName>的项目
以上为初始化一个目录为工作目录,然后使用的流程,你可以使用help命令来查看其他命令。整个工具运行,主要依赖于INIT.plc中的控制脚本。比如我上面所提到的项目的控制脚本如下:
set editor nvim
cmd 0
0 goto /run/media/qingyu/Making/Arukas_Java/src/main/java/com/qingyu
0 editor Arukas.java
cmd 1
1 goto .
1 type ./gradlew run
它看起来稍微有点像汇编语言,符合两种可能的结构:指令+参数(s)、操作目标+指令+参数(s),这里的(s)指参数可能有多个。接下来我会介绍你应该如何编写一个这样的脚本。
通常在开头,或者启动一个终端前,你应该设置你将要使用的编辑器命令,像这样:
set editor <EditorName>
需要注意的是,如果你的结构符合指令+参数,你需要在指令前添加两个空格(Space),否则指令分割工作会不正常。以及,必须将第二个参数写为“editor”,实际上这是一个非常简易的变量系统,只是我们硬编码了editor作为预留,并且没有实现获取变量值这个功能。
接着,你需要启动终端:
cmd <Num>
这条命令会启动一个终端,并将其编号为
然后你就可以开始构建流程了,这里是你可以使用的一些指令:
| Command | Usage |
| -------------- | -------------------------------------------------- |
| goto | 切换至传入的目录 |
| editor | 启动设置的编辑器,打开传入的文件 |
| type | 将指令后所有字符作为内容输入终端,
这些指令应该足够大多数场景使用,而有两个特殊指令,flagstart与flagend,用于简单的选择执行。比如这个例子:
set editor nvim
cmd 0
0 goto .
0 editor init.lua
flagstart plugins
cmd 1
1 goto /home/qingyu/.config/nvim/lua/config
1 editor lazy.lua
flagend
在这个脚本中,如果使用pl run
以上为大致的编写流程。在进入使用流程前,请注意,这个工具并不是严格的按照防御性编程策略进行开发的,所以格式要求非常严格,并且实际上,每一个指令的传参数量都没有限制,只是没有使用多余的参数而已,但是依旧不建议你传入过多参数,我也不知道会发生什么。以及,这个工具完全是为了配合我的工作方式而设计的,所以只有一些参考作用,甚至根本就只是个人的项目展示。如果你真的很想试试,你需要使用X Server作为窗口系统,并且以下代码有需要修改的部分,比如cmd指令的实现,我们通过Ctrl+alt+T快捷键调起终端,你可能需要自己修改: 首先你需要安装xdotool,你可以用apt、dnf等包管理器安装它。只要你在终端中使用xdotool命令,显示的是命令列表,那就是安装成功了。 接着需要创建三个文件,"main.py"、"parser.py"、"WorkSpaces.txt"。然后在main.py中输入以下内容:
from parser import ParseCommand
from sys import exit,argv
from os import getcwd,listdir,remove,mkdir
from os.path import exists
def CheckArgv(Index:int,msg:str):
try:
argv[Index]
except:
print(msg)
exit(1)
def ReadWorkSpaces():
f=open(argv[0].replace("main.py","")+"WorkSpaces.txt","r",encoding="utf-8")
Raw=f.readlines()
f.close()
Name=[]
Path=[]
for line in Raw:
Name.append(line.split(" ")[0])
Path.append(line.split(" ")[1].replace("\n",""))
return dict(zip(Name,Path))
CheckArgv(1,"Usage:\n pl <command> [options]\nCommands:\n init Initialize current floder as workspace.\n run open workspace.\n delete Delete a workspace\n list list usable workspaces\n new create new workspace")
if argv[1] == "init":
CheckArgv(2,"Workpace must have a name!")
path=getcwd()
if "INIT.plc" in listdir(path):
print("This folder was already initialized!")
exit(0)
f=open(path+"/INIT.plc","w")
f.close()
f1=open(argv[0].replace("main.py","")+"WorkSpaces.txt","a",encoding="utf-8")
f1.write(argv[2]+" "+path+"\n")
f1.close()
elif argv[1] == "run":
CheckArgv(2,"Lost workspace name!")
Workspaces=ReadWorkSpaces()
if argv[2] in Workspaces.keys():
ParseCommand(Workspaces[argv[2]]+"/INIT.plc",argv[2],argv[2:])
else:
print("WorkSpace "+argv[2]+" not found!")
elif argv[1] == "delete":
CheckArgv(2,"Lost delete workspace name!")
if not argv[2] in ReadWorkSpaces():
print("Can't found workspace:"+argv[2])
exit(1)
print("This operate WON'T delete your workspace folder,if you want delete your folder,please do it mannualy.")
if input("Are you sure you want to delete workspace "+argv[2]+"? (Y/n)").upper() == "Y":
try:
Path=ReadWorkSpaces()[argv[2]]
if exists(Path+"/INIT.plc"):
remove(Path+"/INIT.plc")
print("Success delete "+argv[2]+".")
except:
print("Fail to remove INIT.plc file in "+Path+" you can try to do this mannualy.")
exit(1)
f2=open(argv[0].replace("main.py","")+"WorkSpaces.txt","r",encoding="utf-8")
RawWork=f2.readlines()
f2.close()
f3=open(argv[0].replace("main.py","")+"WorkSpaces.txt","w",encoding="utf-8")
for i in RawWork:
if i.split(" ")[0] != argv[2]:
f3.write(i)
elif argv[1] == "list":
print("Name Path")
for i in ReadWorkSpaces().keys():
out=i.ljust(25," ")+ReadWorkSpaces()[i]
print(out)
elif argv[1] == "new":
CheckArgv(2,"WorkSpace must have a name!")
try:
mkdir(argv[2])
print("Created folder "+argv[2]+"!")
except FileExistsError:
print("The folder is exists!")
exit(1)
try:
path=getcwd()+"/"+argv[2]
f=open(path+"/INIT.plc","w")
f.close()
f1=open(argv[0].replace("main.py","")+"WorkSpaces.txt","a",encoding="utf-8")
f1.write(argv[2]+" "+path+"\n")
f1.close()
except:
print("can't create INIT.plc!You can try to do this mannualy.")
exit(1)
else:
print("Unknown command "+argv[1])
然后在parser.py中输入以下内容。:
from sys import exit
from os.path import exists
from os import system
from time import sleep
def CheckArgsAmount(Command:list,Amount:int):
return len(Command)>=Amount
def ParseCommand(Path:str,PrjName:str,FLAGs:list):
f=open(Path,"r",encoding="utf-8")
Raw=f.readlines()
f.close()
UseableCommand=["goto","editor","set","cmd","type"]
Vars={}
Terminals=[]
N_FLAG=["Normal"]#You can think this as a stack
for flag in FLAGs:
FLAGs[FLAGs.index(flag)] = flag.replace("--","")
for line in Raw:
line=line.replace("\n","")
if line[0]==" ":
Command=line.split(" ")[2:]
else:
Command=line.split(" ")
if not CheckArgsAmount(Command,2) and Command[0]!="flagend":
print("Invaild syntax in line:"+line)
exit(1)
print(Command)
if Command[0] == "cmd":
sleep(0.7)
if Command[0] == "flagstart":
N_FLAG.append(Command[1])#Push the FLAG into the stack
continue
if Command[0] == "flagend":
del N_FLAG[-1]#Pop out the last FLAG
continue
if N_FLAG[-1] in FLAGs or N_FLAG[-1]=="Normal":
if not Command[0].isdigit() and Command[0] in UseableCommand:
if Command[0]=="set":
if CheckArgsAmount(Command,3):
Vars[Command[1]]=Command[2]
else:
print("Command 'set' need at least 2 agrs!")
exit(1)
if Command[0]=="cmd":
if not CheckArgsAmount(Command,2):
print("Command 'cmd' need at least 1 args!")
exit(1)
else:
system("xdotool key ctrl+alt+t")
sleep(0.7)
system("xdotool key ctrl+shift+s && xdotool type '"+PrjName+"-"+Command[1]+"' && xdotool key Return")
Terminals.append(Command[1])
elif Command[0].isdigit() and Command[1] in UseableCommand:
if Command[1]=="goto":
if not CheckArgsAmount(Command,3):
print("Command 'goto' need at least 1 agrs!")
exit(1)
if Command[2]==".":
Command[2]=Path.replace("/INIT.plc","")
if not exists(Command[2]):
print("Path '"+Command[2]+"' not exist!")
exit(1)
if not Command[0] in Terminals:
print("Can't found Terminal"+Command[0])
exit(1)
system("xdotool search '"+PrjName+"-"+Command[0]+"' windowactivate && xdotool type 'cd "+Command[2]+"' && xdotool key Return")
if Command[1]=="editor":
try:
system("xdotool type '"+Vars['editor']+" "+Command[2]+"' && xdotool key Return")
except:
print("Var editor not found!")
exit(1)
if Command[1]=="type":
if not CheckArgsAmount(Command,3):
print("Command 'type' need at least 1 args!")
exit(1)
system("xdotool search '"+PrjName+"-"+Command[0]+"' windowactivate && sleep .2 && xdotool type '"+" ".join(Command[2:]).replace("<CR>","")+"'")
if "<CR>" in "".join(Command[2:]):
system("xdotool search '"+PrjName+"-"+Command[0]+"' windowactivate && sleep .2 && xdotool key Return")
if Command[1]=="key":
if not CheckArgsAmount(Command,3):
print("Command 'key' need at least 1 args!")
exit(1)
system("xdotool search '"+PrjName+"-"+Command[0]+"' windowactivate && sleep .2 && xdotool key "+Command[2])
else:
print("Unknow command in line:"+line)
exit(1)
最后,在你的"~/.bashrc"的末尾添加如下内容,其中的python字段请自行修改为你的python命令:
pl() {
python /run/media/qingyu/Making/ProjectLauncher/main.py $@
}
然后你应该可以在新的终端中使用pl指令了。我建议首先使用help指令查看用法。 最后,我还是想说明,这个项目只是一个个人工具,我个人虽然用的很好,但是对于不熟悉的人,可能用的很难受,所以更多是作为交流用,如果产生了什么不良影响,本人概不负责,感谢。
Par QingYu, Catégorie : misc
Tags : Proggraming / Personal-Projects /
Autres articles
C2_Use Jpype+LibGDX to develop a game
Le 六 28 二月 2026
按照上一篇所说,我会在这一篇中,较为详细的说明,如何使用Jpype+LibGDX这样的技术栈进行一些开发。注意,我不建议你把它用在生产中,但是你都固执到想要 …
Par QingYu, Catégorie : misc
Lire la suite …C1_StarBeats
Le 六 28 二月 2026
Introduce my Programming projects.