mirror of
https://github.com/N1ngYu/SaltBot.git
synced 2025-09-28 08:42:40 +08:00
Move to Fix Plugin Load Issue
This commit is contained in:
@@ -50,76 +50,33 @@ up_list = on_command('uplist', priority=20, permission=SUPERUSER)
|
||||
user_img = on_command('myimg',aliases={'盒','看看你的'} ,priority=20, rule=check_time)
|
||||
upload_score = on_command('amusic',aliases={'传分'}, priority=20, rule=check_time)
|
||||
|
||||
music_data = ({
|
||||
"musicId": None,
|
||||
"level": None,
|
||||
"playCount": None,
|
||||
"achievement": None,
|
||||
"comboStatus": None,
|
||||
"syncStatus": None,
|
||||
"deluxscoreMax": None,
|
||||
"scoreRank": None,
|
||||
"extNum1": 0
|
||||
})
|
||||
|
||||
@upload_score.handle()
|
||||
async def handle_firstfunc(event: GroupMessageEvent,args: Message = Arg()):
|
||||
if args:
|
||||
await upload_score.send("1")
|
||||
else:
|
||||
await upload_score.send("请输入乐曲ID")
|
||||
#Powered by Project Fragrance
|
||||
|
||||
@upload_score.got("music_id", prompt="请输入乐曲id")
|
||||
async def got_id(state: T_State):
|
||||
music_data["musicId"] = state["music_id"]
|
||||
await upload_score.send("请输入乐曲等级(0=Basic,1=Advanced,2=Expert,3=Master,4=Re:Master)")
|
||||
#Powered by Project Fragrance
|
||||
@upload_score.got("music_level", prompt="请输入乐曲等级(0=Basic,1=Advanced,2=Expert,3=Master,4=Re:Master)")
|
||||
async def got_level(state: T_State):
|
||||
music_data["level"] = state["music_level"]
|
||||
await upload_score.send("请输入乐曲游玩次数")
|
||||
#Powered by Project Fragrance
|
||||
@upload_score.got("music_count", prompt="请输入乐曲游玩次数")
|
||||
async def got_count(state: T_State):
|
||||
music_data["playCount"] = state["music_count"]
|
||||
await upload_score.send("请输入乐曲acc")
|
||||
#Powered by Project Fragrance
|
||||
@upload_score.got("music_acc", prompt="请输入乐曲acc")
|
||||
async def got_acc(state: T_State):
|
||||
music_data["achievement"] = state["music_acc"]
|
||||
await upload_score.send("请输入乐曲combo状态(0=无,1=FC,2=FC+,3=AP,4=AP+)")
|
||||
#Powered by Project Fragrance
|
||||
@upload_score.got("music_combo", prompt="请输入乐曲combo状态((0=无,1=FC,2=FC+,3=AP,4=AP+)")
|
||||
async def got_combo(state: T_State):
|
||||
music_data["comboStatus"] = state["music_combo"]
|
||||
await upload_score.send("请输入乐曲sync状态(0=无,1=FS,2=FS+,3=FDX,4=FDX+,5=SYNCPLAY)")
|
||||
#Powered by Project Fragrance
|
||||
@upload_score.got("music_sync", prompt="请输入乐曲sync状态((0=无,1=FS,2=FS+,3=FDX,4=FDX+,5=SYNCPLAY)")
|
||||
async def got_sync(state: T_State):
|
||||
music_data["syncStatus"] = state["music_sync"]
|
||||
await upload_score.send("请输入乐曲dx分")
|
||||
#Powered by Project Fragrance
|
||||
@upload_score.got("music_dx", prompt="请输入乐曲dx分")
|
||||
async def got_dx(state: T_State):
|
||||
music_data["deluxscoreMax"] = state["music_dx"]
|
||||
await upload_score.send("请输入rank(13=SSS+,12=SSS,以此类推)")
|
||||
#Powered by Project Fragrance
|
||||
@upload_score.got("music_rank", prompt="请输入rank(13=SSS+,12=SSS,以此类推)")
|
||||
async def got_rank(state: T_State,event: GroupMessageEvent):
|
||||
async def _(event: GroupMessageEvent, message: Message = EventMessage()):
|
||||
user_qq = event.get_user_id()
|
||||
user_id = None
|
||||
if is_userid_exist(user_qq):
|
||||
user_id = get_userid(user_qq)
|
||||
else:
|
||||
await seeme.send("你还没有绑定舞萌账号~\n请先发送「maibind+你的登录二维码识别后字符串」绑定账号吧~")
|
||||
music_data["scoreRank"] = state["music_rank"]
|
||||
music_finish = music(music_data,user_id)
|
||||
if music_finish["is_success"]:
|
||||
await upload_score.send("上传成功~")
|
||||
else:
|
||||
await upload_score.send("出错了...请联系机修")
|
||||
#Powered by Project Fragrance
|
||||
if user_id != -1:
|
||||
music_data = str(message).strip()
|
||||
music_list = music_data.split()
|
||||
if len(music_list) < 9:
|
||||
await seeme.send("指令格式出错啦!请按照以下格式发送(括号中内容无需发送):\namusic 417 0 11 1005245 1 0 12 13\n指令解释:amusic [曲目ID] [乐曲等级] [游玩次数] [Acc] [ComboStatus] [SyncStatus] [DXScore] [Rank]\n乐曲等级(0=Basic,1=Advanced,2=Expert,3=Master,4=Re:Master)\nComboStatus(0=无,1=FC,2=FC+,3=AP,4=AP+)\nSyncStatus(0=无,1=FS,2=FS+,3=FDX,4=FDX+,5=SYNCPLAY)\nRank(0=D,1=C,2=B,3=BB,4=BBB,5=A,6=AA,7=AAA,8=S,9=S+,10=SS,11=SS+,12=SSS,13=SSS+)\n!Caution!DX分数不要超过该乐曲总值,若不确定请自行查询后再使用!")
|
||||
command = music_list[0] # 命令
|
||||
music_id = int(music_list[1]) # 第一个数字
|
||||
music_level = int(music_list[2]) # 第二个数字
|
||||
music_count = int(music_list[3]) # 第三个数字
|
||||
music_acc = int(music_list[4]) # 第四个数字
|
||||
music_combo = int(music_list[5]) # 第五个数字
|
||||
music_sync = int(music_list[6]) # 第六个数字
|
||||
music_dx = int(music_list[7]) # 第七个数字
|
||||
music_rank = int(music_list[8]) # 第八个数字
|
||||
music_return = music(music_id,music_level,music_count,music_acc,music_combo,music_sync,music_dx,music_rank,user_id)
|
||||
if music_return['is_success']:
|
||||
await upload_score.send("上传成功!")
|
||||
else:
|
||||
await upload_score.send("上传失败了...请找机修吧...")
|
||||
|
||||
'''
|
||||
@upload_score.got("music_rank", prompt="请输入乐曲分数排名")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,7 +3,7 @@ import pytz
|
||||
import time
|
||||
import random
|
||||
|
||||
from sdgb import sdgb_api
|
||||
from nonebot_plugin_maimai_helper.leak.sdgb import sdgb_api
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
#from settings import userId
|
||||
@@ -16,8 +16,8 @@ placeName = "插电师电玩北京西单大悦城店"
|
||||
clientId = "A63E01C2805"
|
||||
|
||||
|
||||
from login import login
|
||||
from logout import logout
|
||||
from nonebot_plugin_maimai_helper.leak.login import login
|
||||
from nonebot_plugin_maimai_helper.leak.logout import logout
|
||||
|
||||
def CalcRandom():
|
||||
max = 1037933
|
||||
@@ -34,26 +34,26 @@ def CalcRandom():
|
||||
|
||||
timestamp = int(time.time()) - 60
|
||||
|
||||
def music(music_data,userId):
|
||||
def music(music_id,music_level,music_count,music_acc,music_combo,music_sync,music_dx,music_rank,userId):
|
||||
result_success = {"is_success": False}
|
||||
|
||||
music = music_data
|
||||
# music = music_data
|
||||
|
||||
musicId = music['musicId']
|
||||
level = music['level']
|
||||
playCount = music['playCount']
|
||||
achievement = music['achievement']
|
||||
comboStatus = music['comboStatus']
|
||||
syncStatus = music['syncStatus']
|
||||
deluxscoreMax = music['deluxscoreMax']
|
||||
scoreRank = music['scoreRank']
|
||||
extNum1 = music['extNum1']
|
||||
musicId = music_id
|
||||
level = music_level
|
||||
playCount = music_count
|
||||
achievement = music_acc
|
||||
comboStatus = music_combo
|
||||
syncStatus = music_sync
|
||||
deluxscoreMax = music_dx
|
||||
scoreRank = music_rank
|
||||
extNum1 = 0
|
||||
|
||||
print(timestamp)
|
||||
|
||||
# UserLogin
|
||||
|
||||
login_result = json.loads(login(timestamp))
|
||||
login_result = json.loads(login(timestamp,userId))
|
||||
print(login_result)
|
||||
|
||||
login_id = login_result['loginId']
|
||||
@@ -653,7 +653,8 @@ def music(music_data,userId):
|
||||
"isNewFriendSeasonRankingList": ""
|
||||
}
|
||||
})
|
||||
|
||||
logout1 = logout(timestamp,userId)
|
||||
print(logout1)
|
||||
# userall_result = json.loads(sdgb_api(data, "UpsertUserAllApi", userId))
|
||||
result_success["is_success"] = True
|
||||
return result_success
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,760 @@
|
||||
@Switch01
|
||||
A_Rog
|
||||
Aakanksha Agrawal
|
||||
Abhinav Sagar
|
||||
ABHYUDAY PRATAP SINGH
|
||||
abs51295
|
||||
AceGentile
|
||||
Adam Chainz
|
||||
Adam Tse
|
||||
Adam Wentz
|
||||
admin
|
||||
Adrien Morison
|
||||
ahayrapetyan
|
||||
Ahilya
|
||||
AinsworthK
|
||||
Akash Srivastava
|
||||
Alan Yee
|
||||
Albert Tugushev
|
||||
Albert-Guan
|
||||
albertg
|
||||
Alberto Sottile
|
||||
Aleks Bunin
|
||||
Ales Erjavec
|
||||
Alethea Flowers
|
||||
Alex Gaynor
|
||||
Alex Grönholm
|
||||
Alex Hedges
|
||||
Alex Loosley
|
||||
Alex Morega
|
||||
Alex Stachowiak
|
||||
Alexander Shtyrov
|
||||
Alexandre Conrad
|
||||
Alexey Popravka
|
||||
Aleš Erjavec
|
||||
Alli
|
||||
Ami Fischman
|
||||
Ananya Maiti
|
||||
Anatoly Techtonik
|
||||
Anders Kaseorg
|
||||
Andre Aguiar
|
||||
Andreas Lutro
|
||||
Andrei Geacar
|
||||
Andrew Gaul
|
||||
Andrew Shymanel
|
||||
Andrey Bienkowski
|
||||
Andrey Bulgakov
|
||||
Andrés Delfino
|
||||
Andy Freeland
|
||||
Andy Kluger
|
||||
Ani Hayrapetyan
|
||||
Aniruddha Basak
|
||||
Anish Tambe
|
||||
Anrs Hu
|
||||
Anthony Sottile
|
||||
Antoine Musso
|
||||
Anton Ovchinnikov
|
||||
Anton Patrushev
|
||||
Antonio Alvarado Hernandez
|
||||
Antony Lee
|
||||
Antti Kaihola
|
||||
Anubhav Patel
|
||||
Anudit Nagar
|
||||
Anuj Godase
|
||||
AQNOUCH Mohammed
|
||||
AraHaan
|
||||
Arindam Choudhury
|
||||
Armin Ronacher
|
||||
Artem
|
||||
Arun Babu Neelicattu
|
||||
Ashley Manton
|
||||
Ashwin Ramaswami
|
||||
atse
|
||||
Atsushi Odagiri
|
||||
Avinash Karhana
|
||||
Avner Cohen
|
||||
Awit (Ah-Wit) Ghirmai
|
||||
Baptiste Mispelon
|
||||
Barney Gale
|
||||
barneygale
|
||||
Bartek Ogryczak
|
||||
Bastian Venthur
|
||||
Ben Bodenmiller
|
||||
Ben Darnell
|
||||
Ben Hoyt
|
||||
Ben Mares
|
||||
Ben Rosser
|
||||
Bence Nagy
|
||||
Benjamin Peterson
|
||||
Benjamin VanEvery
|
||||
Benoit Pierre
|
||||
Berker Peksag
|
||||
Bernard
|
||||
Bernard Tyers
|
||||
Bernardo B. Marques
|
||||
Bernhard M. Wiedemann
|
||||
Bertil Hatt
|
||||
Bhavam Vidyarthi
|
||||
Blazej Michalik
|
||||
Bogdan Opanchuk
|
||||
BorisZZZ
|
||||
Brad Erickson
|
||||
Bradley Ayers
|
||||
Brandon L. Reiss
|
||||
Brandt Bucher
|
||||
Brett Randall
|
||||
Brett Rosen
|
||||
Brian Cristante
|
||||
Brian Rosner
|
||||
briantracy
|
||||
BrownTruck
|
||||
Bruno Oliveira
|
||||
Bruno Renié
|
||||
Bruno S
|
||||
Bstrdsmkr
|
||||
Buck Golemon
|
||||
burrows
|
||||
Bussonnier Matthias
|
||||
bwoodsend
|
||||
c22
|
||||
Caleb Martinez
|
||||
Calvin Smith
|
||||
Carl Meyer
|
||||
Carlos Liam
|
||||
Carol Willing
|
||||
Carter Thayer
|
||||
Cass
|
||||
Chandrasekhar Atina
|
||||
Chih-Hsuan Yen
|
||||
Chris Brinker
|
||||
Chris Hunt
|
||||
Chris Jerdonek
|
||||
Chris Kuehl
|
||||
Chris McDonough
|
||||
Chris Pawley
|
||||
Chris Pryer
|
||||
Chris Wolfe
|
||||
Christian Clauss
|
||||
Christian Heimes
|
||||
Christian Oudard
|
||||
Christoph Reiter
|
||||
Christopher Hunt
|
||||
Christopher Snyder
|
||||
cjc7373
|
||||
Clark Boylan
|
||||
Claudio Jolowicz
|
||||
Clay McClure
|
||||
Cody
|
||||
Cody Soyland
|
||||
Colin Watson
|
||||
Collin Anderson
|
||||
Connor Osborn
|
||||
Cooper Lees
|
||||
Cooper Ry Lees
|
||||
Cory Benfield
|
||||
Cory Wright
|
||||
Craig Kerstiens
|
||||
Cristian Sorinel
|
||||
Cristina
|
||||
Cristina Muñoz
|
||||
Curtis Doty
|
||||
cytolentino
|
||||
Daan De Meyer
|
||||
Dale
|
||||
Damian
|
||||
Damian Quiroga
|
||||
Damian Shaw
|
||||
Dan Black
|
||||
Dan Savilonis
|
||||
Dan Sully
|
||||
Dane Hillard
|
||||
daniel
|
||||
Daniel Collins
|
||||
Daniel Hahler
|
||||
Daniel Holth
|
||||
Daniel Jost
|
||||
Daniel Katz
|
||||
Daniel Shaulov
|
||||
Daniele Esposti
|
||||
Daniele Nicolodi
|
||||
Daniele Procida
|
||||
Daniil Konovalenko
|
||||
Danny Hermes
|
||||
Danny McClanahan
|
||||
Darren Kavanagh
|
||||
Dav Clark
|
||||
Dave Abrahams
|
||||
Dave Jones
|
||||
David Aguilar
|
||||
David Black
|
||||
David Bordeynik
|
||||
David Caro
|
||||
David D Lowe
|
||||
David Evans
|
||||
David Hewitt
|
||||
David Linke
|
||||
David Poggi
|
||||
David Pursehouse
|
||||
David Runge
|
||||
David Tucker
|
||||
David Wales
|
||||
Davidovich
|
||||
ddelange
|
||||
Deepak Sharma
|
||||
Deepyaman Datta
|
||||
Denise Yu
|
||||
dependabot[bot]
|
||||
derwolfe
|
||||
Desetude
|
||||
Devesh Kumar Singh
|
||||
Diego Caraballo
|
||||
Diego Ramirez
|
||||
DiegoCaraballo
|
||||
Dimitri Merejkowsky
|
||||
Dimitri Papadopoulos
|
||||
Dirk Stolle
|
||||
Dmitry Gladkov
|
||||
Dmitry Volodin
|
||||
Domen Kožar
|
||||
Dominic Davis-Foster
|
||||
Donald Stufft
|
||||
Dongweiming
|
||||
doron zarhi
|
||||
Dos Moonen
|
||||
Douglas Thor
|
||||
DrFeathers
|
||||
Dustin Ingram
|
||||
Dwayne Bailey
|
||||
Ed Morley
|
||||
Edgar Ramírez
|
||||
Edgar Ramírez Mondragón
|
||||
Ee Durbin
|
||||
Efflam Lemaillet
|
||||
efflamlemaillet
|
||||
Eitan Adler
|
||||
ekristina
|
||||
elainechan
|
||||
Eli Schwartz
|
||||
Elisha Hollander
|
||||
Ellen Marie Dash
|
||||
Emil Burzo
|
||||
Emil Styrke
|
||||
Emmanuel Arias
|
||||
Endoh Takanao
|
||||
enoch
|
||||
Erdinc Mutlu
|
||||
Eric Cousineau
|
||||
Eric Gillingham
|
||||
Eric Hanchrow
|
||||
Eric Hopper
|
||||
Erik M. Bray
|
||||
Erik Rose
|
||||
Erwin Janssen
|
||||
Eugene Vereshchagin
|
||||
everdimension
|
||||
Federico
|
||||
Felipe Peter
|
||||
Felix Yan
|
||||
fiber-space
|
||||
Filip Kokosiński
|
||||
Filipe Laíns
|
||||
Finn Womack
|
||||
finnagin
|
||||
Flavio Amurrio
|
||||
Florian Briand
|
||||
Florian Rathgeber
|
||||
Francesco
|
||||
Francesco Montesano
|
||||
Frost Ming
|
||||
Gabriel Curio
|
||||
Gabriel de Perthuis
|
||||
Garry Polley
|
||||
gavin
|
||||
gdanielson
|
||||
Geoffrey Sneddon
|
||||
George Song
|
||||
Georgi Valkov
|
||||
Georgy Pchelkin
|
||||
ghost
|
||||
Giftlin Rajaiah
|
||||
gizmoguy1
|
||||
gkdoc
|
||||
Godefroid Chapelle
|
||||
Gopinath M
|
||||
GOTO Hayato
|
||||
gousaiyang
|
||||
gpiks
|
||||
Greg Roodt
|
||||
Greg Ward
|
||||
Guilherme Espada
|
||||
Guillaume Seguin
|
||||
gutsytechster
|
||||
Guy Rozendorn
|
||||
Guy Tuval
|
||||
gzpan123
|
||||
Hanjun Kim
|
||||
Hari Charan
|
||||
Harsh Vardhan
|
||||
harupy
|
||||
Harutaka Kawamura
|
||||
hauntsaninja
|
||||
Henrich Hartzer
|
||||
Henry Schreiner
|
||||
Herbert Pfennig
|
||||
Holly Stotelmyer
|
||||
Honnix
|
||||
Hsiaoming Yang
|
||||
Hugo Lopes Tavares
|
||||
Hugo van Kemenade
|
||||
Hugues Bruant
|
||||
Hynek Schlawack
|
||||
Ian Bicking
|
||||
Ian Cordasco
|
||||
Ian Lee
|
||||
Ian Stapleton Cordasco
|
||||
Ian Wienand
|
||||
Igor Kuzmitshov
|
||||
Igor Sobreira
|
||||
Ilan Schnell
|
||||
Illia Volochii
|
||||
Ilya Baryshev
|
||||
Inada Naoki
|
||||
Ionel Cristian Mărieș
|
||||
Ionel Maries Cristian
|
||||
Itamar Turner-Trauring
|
||||
Ivan Pozdeev
|
||||
J. Nick Koston
|
||||
Jacob Kim
|
||||
Jacob Walls
|
||||
Jaime Sanz
|
||||
jakirkham
|
||||
Jakub Kuczys
|
||||
Jakub Stasiak
|
||||
Jakub Vysoky
|
||||
Jakub Wilk
|
||||
James Cleveland
|
||||
James Curtin
|
||||
James Firth
|
||||
James Gerity
|
||||
James Polley
|
||||
Jan Pokorný
|
||||
Jannis Leidel
|
||||
Jarek Potiuk
|
||||
jarondl
|
||||
Jason Curtis
|
||||
Jason R. Coombs
|
||||
JasonMo
|
||||
JasonMo1
|
||||
Jay Graves
|
||||
Jean Abou Samra
|
||||
Jean-Christophe Fillion-Robin
|
||||
Jeff Barber
|
||||
Jeff Dairiki
|
||||
Jeff Widman
|
||||
Jelmer Vernooij
|
||||
jenix21
|
||||
Jeremy Stanley
|
||||
Jeremy Zafran
|
||||
Jesse Rittner
|
||||
Jiashuo Li
|
||||
Jim Fisher
|
||||
Jim Garrison
|
||||
Jiun Bae
|
||||
Jivan Amara
|
||||
Joe Bylund
|
||||
Joe Michelini
|
||||
John Paton
|
||||
John T. Wodder II
|
||||
John-Scott Atlakson
|
||||
johnthagen
|
||||
Jon Banafato
|
||||
Jon Dufresne
|
||||
Jon Parise
|
||||
Jonas Nockert
|
||||
Jonathan Herbert
|
||||
Joonatan Partanen
|
||||
Joost Molenaar
|
||||
Jorge Niedbalski
|
||||
Joseph Bylund
|
||||
Joseph Long
|
||||
Josh Bronson
|
||||
Josh Hansen
|
||||
Josh Schneier
|
||||
Joshua
|
||||
Juan Luis Cano Rodríguez
|
||||
Juanjo Bazán
|
||||
Judah Rand
|
||||
Julian Berman
|
||||
Julian Gethmann
|
||||
Julien Demoor
|
||||
Jussi Kukkonen
|
||||
jwg4
|
||||
Jyrki Pulliainen
|
||||
Kai Chen
|
||||
Kai Mueller
|
||||
Kamal Bin Mustafa
|
||||
kasium
|
||||
kaustav haldar
|
||||
keanemind
|
||||
Keith Maxwell
|
||||
Kelsey Hightower
|
||||
Kenneth Belitzky
|
||||
Kenneth Reitz
|
||||
Kevin Burke
|
||||
Kevin Carter
|
||||
Kevin Frommelt
|
||||
Kevin R Patterson
|
||||
Kexuan Sun
|
||||
Kit Randel
|
||||
Klaas van Schelven
|
||||
KOLANICH
|
||||
kpinc
|
||||
Krishna Oza
|
||||
Kumar McMillan
|
||||
Kurt McKee
|
||||
Kyle Persohn
|
||||
lakshmanaram
|
||||
Laszlo Kiss-Kollar
|
||||
Laurent Bristiel
|
||||
Laurent LAPORTE
|
||||
Laurie O
|
||||
Laurie Opperman
|
||||
layday
|
||||
Leon Sasson
|
||||
Lev Givon
|
||||
Lincoln de Sousa
|
||||
Lipis
|
||||
lorddavidiii
|
||||
Loren Carvalho
|
||||
Lucas Cimon
|
||||
Ludovic Gasc
|
||||
Lukas Geiger
|
||||
Lukas Juhrich
|
||||
Luke Macken
|
||||
Luo Jiebin
|
||||
luojiebin
|
||||
luz.paz
|
||||
László Kiss Kollár
|
||||
M00nL1ght
|
||||
Marc Abramowitz
|
||||
Marc Tamlyn
|
||||
Marcus Smith
|
||||
Mariatta
|
||||
Mark Kohler
|
||||
Mark Williams
|
||||
Markus Hametner
|
||||
Martey Dodoo
|
||||
Martin Fischer
|
||||
Martin Häcker
|
||||
Martin Pavlasek
|
||||
Masaki
|
||||
Masklinn
|
||||
Matej Stuchlik
|
||||
Mathew Jennings
|
||||
Mathieu Bridon
|
||||
Mathieu Kniewallner
|
||||
Matt Bacchi
|
||||
Matt Good
|
||||
Matt Maker
|
||||
Matt Robenolt
|
||||
matthew
|
||||
Matthew Einhorn
|
||||
Matthew Feickert
|
||||
Matthew Gilliard
|
||||
Matthew Iversen
|
||||
Matthew Treinish
|
||||
Matthew Trumbell
|
||||
Matthew Willson
|
||||
Matthias Bussonnier
|
||||
mattip
|
||||
Maurits van Rees
|
||||
Max W Chase
|
||||
Maxim Kurnikov
|
||||
Maxime Rouyrre
|
||||
mayeut
|
||||
mbaluna
|
||||
mdebi
|
||||
memoselyk
|
||||
meowmeowcat
|
||||
Michael
|
||||
Michael Aquilina
|
||||
Michael E. Karpeles
|
||||
Michael Klich
|
||||
Michael Mintz
|
||||
Michael Williamson
|
||||
michaelpacer
|
||||
Michał Górny
|
||||
Mickaël Schoentgen
|
||||
Miguel Araujo Perez
|
||||
Mihir Singh
|
||||
Mike
|
||||
Mike Hendricks
|
||||
Min RK
|
||||
MinRK
|
||||
Miro Hrončok
|
||||
Monica Baluna
|
||||
montefra
|
||||
Monty Taylor
|
||||
Muha Ajjan
|
||||
Nadav Wexler
|
||||
Nahuel Ambrosini
|
||||
Nate Coraor
|
||||
Nate Prewitt
|
||||
Nathan Houghton
|
||||
Nathaniel J. Smith
|
||||
Nehal J Wani
|
||||
Neil Botelho
|
||||
Nguyễn Gia Phong
|
||||
Nicholas Serra
|
||||
Nick Coghlan
|
||||
Nick Stenning
|
||||
Nick Timkovich
|
||||
Nicolas Bock
|
||||
Nicole Harris
|
||||
Nikhil Benesch
|
||||
Nikhil Ladha
|
||||
Nikita Chepanov
|
||||
Nikolay Korolev
|
||||
Nipunn Koorapati
|
||||
Nitesh Sharma
|
||||
Niyas Sait
|
||||
Noah
|
||||
Noah Gorny
|
||||
Nowell Strite
|
||||
NtaleGrey
|
||||
nvdv
|
||||
OBITORASU
|
||||
Ofek Lev
|
||||
ofrinevo
|
||||
Oliver Freund
|
||||
Oliver Jeeves
|
||||
Oliver Mannion
|
||||
Oliver Tonnhofer
|
||||
Olivier Girardot
|
||||
Olivier Grisel
|
||||
Ollie Rutherfurd
|
||||
OMOTO Kenji
|
||||
Omry Yadan
|
||||
onlinejudge95
|
||||
Oren Held
|
||||
Oscar Benjamin
|
||||
Oz N Tiram
|
||||
Pachwenko
|
||||
Patrick Dubroy
|
||||
Patrick Jenkins
|
||||
Patrick Lawson
|
||||
patricktokeeffe
|
||||
Patrik Kopkan
|
||||
Paul Ganssle
|
||||
Paul Kehrer
|
||||
Paul Moore
|
||||
Paul Nasrat
|
||||
Paul Oswald
|
||||
Paul van der Linden
|
||||
Paulus Schoutsen
|
||||
Pavel Safronov
|
||||
Pavithra Eswaramoorthy
|
||||
Pawel Jasinski
|
||||
Paweł Szramowski
|
||||
Pekka Klärck
|
||||
Peter Gessler
|
||||
Peter Lisák
|
||||
Peter Waller
|
||||
petr-tik
|
||||
Phaneendra Chiruvella
|
||||
Phil Elson
|
||||
Phil Freo
|
||||
Phil Pennock
|
||||
Phil Whelan
|
||||
Philip Jägenstedt
|
||||
Philip Molloy
|
||||
Philippe Ombredanne
|
||||
Pi Delport
|
||||
Pierre-Yves Rofes
|
||||
Pieter Degroote
|
||||
pip
|
||||
Prabakaran Kumaresshan
|
||||
Prabhjyotsing Surjit Singh Sodhi
|
||||
Prabhu Marappan
|
||||
Pradyun Gedam
|
||||
Prashant Sharma
|
||||
Pratik Mallya
|
||||
pre-commit-ci[bot]
|
||||
Preet Thakkar
|
||||
Preston Holmes
|
||||
Przemek Wrzos
|
||||
Pulkit Goyal
|
||||
q0w
|
||||
Qiangning Hong
|
||||
Qiming Xu
|
||||
Quentin Lee
|
||||
Quentin Pradet
|
||||
R. David Murray
|
||||
Rafael Caricio
|
||||
Ralf Schmitt
|
||||
Razzi Abuissa
|
||||
rdb
|
||||
Reece Dunham
|
||||
Remi Rampin
|
||||
Rene Dudfield
|
||||
Riccardo Magliocchetti
|
||||
Riccardo Schirone
|
||||
Richard Jones
|
||||
Richard Si
|
||||
Ricky Ng-Adam
|
||||
Rishi
|
||||
RobberPhex
|
||||
Robert Collins
|
||||
Robert McGibbon
|
||||
Robert Pollak
|
||||
Robert T. McGibbon
|
||||
robin elisha robinson
|
||||
Roey Berman
|
||||
Rohan Jain
|
||||
Roman Bogorodskiy
|
||||
Roman Donchenko
|
||||
Romuald Brunet
|
||||
ronaudinho
|
||||
Ronny Pfannschmidt
|
||||
Rory McCann
|
||||
Ross Brattain
|
||||
Roy Wellington Ⅳ
|
||||
Ruairidh MacLeod
|
||||
Russell Keith-Magee
|
||||
Ryan Shepherd
|
||||
Ryan Wooden
|
||||
ryneeverett
|
||||
Sachi King
|
||||
Salvatore Rinchiera
|
||||
sandeepkiran-js
|
||||
Sander Van Balen
|
||||
Savio Jomton
|
||||
schlamar
|
||||
Scott Kitterman
|
||||
Sean
|
||||
seanj
|
||||
Sebastian Jordan
|
||||
Sebastian Schaetz
|
||||
Segev Finer
|
||||
SeongSoo Cho
|
||||
Sergey Vasilyev
|
||||
Seth Michael Larson
|
||||
Seth Woodworth
|
||||
Shahar Epstein
|
||||
Shantanu
|
||||
shireenrao
|
||||
Shivansh-007
|
||||
Shlomi Fish
|
||||
Shovan Maity
|
||||
Simeon Visser
|
||||
Simon Cross
|
||||
Simon Pichugin
|
||||
sinoroc
|
||||
sinscary
|
||||
snook92
|
||||
socketubs
|
||||
Sorin Sbarnea
|
||||
Srinivas Nyayapati
|
||||
Stavros Korokithakis
|
||||
Stefan Scherfke
|
||||
Stefano Rivera
|
||||
Stephan Erb
|
||||
Stephen Rosen
|
||||
stepshal
|
||||
Steve (Gadget) Barnes
|
||||
Steve Barnes
|
||||
Steve Dower
|
||||
Steve Kowalik
|
||||
Steven Myint
|
||||
Steven Silvester
|
||||
stonebig
|
||||
studioj
|
||||
Stéphane Bidoul
|
||||
Stéphane Bidoul (ACSONE)
|
||||
Stéphane Klein
|
||||
Sumana Harihareswara
|
||||
Surbhi Sharma
|
||||
Sviatoslav Sydorenko
|
||||
Swat009
|
||||
Sylvain
|
||||
Takayuki SHIMIZUKAWA
|
||||
Taneli Hukkinen
|
||||
tbeswick
|
||||
Thiago
|
||||
Thijs Triemstra
|
||||
Thomas Fenzl
|
||||
Thomas Grainger
|
||||
Thomas Guettler
|
||||
Thomas Johansson
|
||||
Thomas Kluyver
|
||||
Thomas Smith
|
||||
Thomas VINCENT
|
||||
Tim D. Smith
|
||||
Tim Gates
|
||||
Tim Harder
|
||||
Tim Heap
|
||||
tim smith
|
||||
tinruufu
|
||||
Tobias Hermann
|
||||
Tom Forbes
|
||||
Tom Freudenheim
|
||||
Tom V
|
||||
Tomas Hrnciar
|
||||
Tomas Orsava
|
||||
Tomer Chachamu
|
||||
Tommi Enenkel | AnB
|
||||
Tomáš Hrnčiar
|
||||
Tony Beswick
|
||||
Tony Narlock
|
||||
Tony Zhaocheng Tan
|
||||
TonyBeswick
|
||||
toonarmycaptain
|
||||
Toshio Kuratomi
|
||||
toxinu
|
||||
Travis Swicegood
|
||||
Tushar Sadhwani
|
||||
Tzu-ping Chung
|
||||
Valentin Haenel
|
||||
Victor Stinner
|
||||
victorvpaulo
|
||||
Vikram - Google
|
||||
Viktor Szépe
|
||||
Ville Skyttä
|
||||
Vinay Sajip
|
||||
Vincent Philippon
|
||||
Vinicyus Macedo
|
||||
Vipul Kumar
|
||||
Vitaly Babiy
|
||||
Vladimir Fokow
|
||||
Vladimir Rutsky
|
||||
W. Trevor King
|
||||
Wil Tan
|
||||
Wilfred Hughes
|
||||
William Edwards
|
||||
William ML Leslie
|
||||
William T Olson
|
||||
William Woodruff
|
||||
Wilson Mo
|
||||
wim glenn
|
||||
Winson Luk
|
||||
Wolfgang Maier
|
||||
Wu Zhenyu
|
||||
XAMES3
|
||||
Xavier Fernandez
|
||||
xoviat
|
||||
xtreak
|
||||
YAMAMOTO Takashi
|
||||
Yen Chi Hsuan
|
||||
Yeray Diaz Diaz
|
||||
Yoval P
|
||||
Yu Jian
|
||||
Yuan Jing Vincent Yan
|
||||
Yusuke Hayashi
|
||||
Zearin
|
||||
Zhiping Deng
|
||||
ziebam
|
||||
Zvezdan Petkovic
|
||||
Łukasz Langa
|
||||
Роман Донченко
|
||||
Семён Марьясин
|
||||
rekcäH nitraM
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2008-present The pip developers (see AUTHORS.txt file)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,88 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: pip
|
||||
Version: 24.0
|
||||
Summary: The PyPA recommended tool for installing Python packages.
|
||||
Author-email: The pip developers <distutils-sig@python.org>
|
||||
License: MIT
|
||||
Project-URL: Homepage, https://pip.pypa.io/
|
||||
Project-URL: Documentation, https://pip.pypa.io
|
||||
Project-URL: Source, https://github.com/pypa/pip
|
||||
Project-URL: Changelog, https://pip.pypa.io/en/stable/news/
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Topic :: Software Development :: Build Tools
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Requires-Python: >=3.7
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE.txt
|
||||
License-File: AUTHORS.txt
|
||||
|
||||
pip - The Python Package Installer
|
||||
==================================
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/pip.svg
|
||||
:target: https://pypi.org/project/pip/
|
||||
:alt: PyPI
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/pip
|
||||
:target: https://pypi.org/project/pip
|
||||
:alt: PyPI - Python Version
|
||||
|
||||
.. image:: https://readthedocs.org/projects/pip/badge/?version=latest
|
||||
:target: https://pip.pypa.io/en/latest
|
||||
:alt: Documentation
|
||||
|
||||
pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes.
|
||||
|
||||
Please take a look at our documentation for how to install and use pip:
|
||||
|
||||
* `Installation`_
|
||||
* `Usage`_
|
||||
|
||||
We release updates regularly, with a new version every 3 months. Find more details in our documentation:
|
||||
|
||||
* `Release notes`_
|
||||
* `Release process`_
|
||||
|
||||
If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms:
|
||||
|
||||
* `Issue tracking`_
|
||||
* `Discourse channel`_
|
||||
* `User IRC`_
|
||||
|
||||
If you want to get involved head over to GitHub to get the source code, look at our development documentation and feel free to jump on the developer mailing lists and chat rooms:
|
||||
|
||||
* `GitHub page`_
|
||||
* `Development documentation`_
|
||||
* `Development IRC`_
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
Everyone interacting in the pip project's codebases, issue trackers, chat
|
||||
rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
|
||||
|
||||
.. _package installer: https://packaging.python.org/guides/tool-recommendations/
|
||||
.. _Python Package Index: https://pypi.org
|
||||
.. _Installation: https://pip.pypa.io/en/stable/installation/
|
||||
.. _Usage: https://pip.pypa.io/en/stable/
|
||||
.. _Release notes: https://pip.pypa.io/en/stable/news.html
|
||||
.. _Release process: https://pip.pypa.io/en/latest/development/release-process/
|
||||
.. _GitHub page: https://github.com/pypa/pip
|
||||
.. _Development documentation: https://pip.pypa.io/en/latest/development
|
||||
.. _Issue tracking: https://github.com/pypa/pip/issues
|
||||
.. _Discourse channel: https://discuss.python.org/c/packaging
|
||||
.. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa
|
||||
.. _Development IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa-dev
|
||||
.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.42.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[console_scripts]
|
||||
pip = pip._internal.cli.main:main
|
||||
pip3 = pip._internal.cli.main:main
|
||||
pip3.10 = pip._internal.cli.main:main
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,566 @@
|
||||
"""
|
||||
Utilities for determining application-specific dirs. See <https://github.com/platformdirs/platformdirs> for details and
|
||||
usage.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .api import PlatformDirsABC
|
||||
from .version import __version__
|
||||
from .version import __version_tuple__ as __version_info__
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
if sys.version_info >= (3, 8): # pragma: no cover (py38+)
|
||||
from typing import Literal
|
||||
else: # pragma: no cover (py38+)
|
||||
from pip._vendor.typing_extensions import Literal
|
||||
|
||||
|
||||
def _set_platform_dir_class() -> type[PlatformDirsABC]:
|
||||
if sys.platform == "win32":
|
||||
from pip._vendor.platformdirs.windows import Windows as Result
|
||||
elif sys.platform == "darwin":
|
||||
from pip._vendor.platformdirs.macos import MacOS as Result
|
||||
else:
|
||||
from pip._vendor.platformdirs.unix import Unix as Result
|
||||
|
||||
if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system":
|
||||
if os.getenv("SHELL") or os.getenv("PREFIX"):
|
||||
return Result
|
||||
|
||||
from pip._vendor.platformdirs.android import _android_folder
|
||||
|
||||
if _android_folder() is not None:
|
||||
from pip._vendor.platformdirs.android import Android
|
||||
|
||||
return Android # return to avoid redefinition of result
|
||||
|
||||
return Result
|
||||
|
||||
|
||||
PlatformDirs = _set_platform_dir_class() #: Currently active platform
|
||||
AppDirs = PlatformDirs #: Backwards compatibility with appdirs
|
||||
|
||||
|
||||
def user_data_dir(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
roaming: bool = False, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> str:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param roaming: See `roaming <platformdirs.api.PlatformDirsABC.roaming>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: data directory tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
roaming=roaming,
|
||||
ensure_exists=ensure_exists,
|
||||
).user_data_dir
|
||||
|
||||
|
||||
def site_data_dir(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
multipath: bool = False, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> str:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param multipath: See `roaming <platformdirs.api.PlatformDirsABC.multipath>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: data directory shared by users
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
multipath=multipath,
|
||||
ensure_exists=ensure_exists,
|
||||
).site_data_dir
|
||||
|
||||
|
||||
def user_config_dir(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
roaming: bool = False, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> str:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param roaming: See `roaming <platformdirs.api.PlatformDirsABC.roaming>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: config directory tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
roaming=roaming,
|
||||
ensure_exists=ensure_exists,
|
||||
).user_config_dir
|
||||
|
||||
|
||||
def site_config_dir(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
multipath: bool = False, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> str:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param multipath: See `roaming <platformdirs.api.PlatformDirsABC.multipath>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: config directory shared by the users
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
multipath=multipath,
|
||||
ensure_exists=ensure_exists,
|
||||
).site_config_dir
|
||||
|
||||
|
||||
def user_cache_dir(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
opinion: bool = True, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> str:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param opinion: See `roaming <platformdirs.api.PlatformDirsABC.opinion>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: cache directory tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
opinion=opinion,
|
||||
ensure_exists=ensure_exists,
|
||||
).user_cache_dir
|
||||
|
||||
|
||||
def site_cache_dir(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
opinion: bool = True, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> str:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: cache directory tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
opinion=opinion,
|
||||
ensure_exists=ensure_exists,
|
||||
).site_cache_dir
|
||||
|
||||
|
||||
def user_state_dir(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
roaming: bool = False, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> str:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param roaming: See `roaming <platformdirs.api.PlatformDirsABC.roaming>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: state directory tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
roaming=roaming,
|
||||
ensure_exists=ensure_exists,
|
||||
).user_state_dir
|
||||
|
||||
|
||||
def user_log_dir(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
opinion: bool = True, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> str:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param opinion: See `roaming <platformdirs.api.PlatformDirsABC.opinion>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: log directory tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
opinion=opinion,
|
||||
ensure_exists=ensure_exists,
|
||||
).user_log_dir
|
||||
|
||||
|
||||
def user_documents_dir() -> str:
|
||||
""":returns: documents directory tied to the user"""
|
||||
return PlatformDirs().user_documents_dir
|
||||
|
||||
|
||||
def user_downloads_dir() -> str:
|
||||
""":returns: downloads directory tied to the user"""
|
||||
return PlatformDirs().user_downloads_dir
|
||||
|
||||
|
||||
def user_pictures_dir() -> str:
|
||||
""":returns: pictures directory tied to the user"""
|
||||
return PlatformDirs().user_pictures_dir
|
||||
|
||||
|
||||
def user_videos_dir() -> str:
|
||||
""":returns: videos directory tied to the user"""
|
||||
return PlatformDirs().user_videos_dir
|
||||
|
||||
|
||||
def user_music_dir() -> str:
|
||||
""":returns: music directory tied to the user"""
|
||||
return PlatformDirs().user_music_dir
|
||||
|
||||
|
||||
def user_runtime_dir(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
opinion: bool = True, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> str:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: runtime directory tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
opinion=opinion,
|
||||
ensure_exists=ensure_exists,
|
||||
).user_runtime_dir
|
||||
|
||||
|
||||
def user_data_path(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
roaming: bool = False, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> Path:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param roaming: See `roaming <platformdirs.api.PlatformDirsABC.roaming>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: data path tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
roaming=roaming,
|
||||
ensure_exists=ensure_exists,
|
||||
).user_data_path
|
||||
|
||||
|
||||
def site_data_path(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
multipath: bool = False, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> Path:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param multipath: See `multipath <platformdirs.api.PlatformDirsABC.multipath>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: data path shared by users
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
multipath=multipath,
|
||||
ensure_exists=ensure_exists,
|
||||
).site_data_path
|
||||
|
||||
|
||||
def user_config_path(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
roaming: bool = False, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> Path:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param roaming: See `roaming <platformdirs.api.PlatformDirsABC.roaming>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: config path tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
roaming=roaming,
|
||||
ensure_exists=ensure_exists,
|
||||
).user_config_path
|
||||
|
||||
|
||||
def site_config_path(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
multipath: bool = False, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> Path:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param multipath: See `roaming <platformdirs.api.PlatformDirsABC.multipath>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: config path shared by the users
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
multipath=multipath,
|
||||
ensure_exists=ensure_exists,
|
||||
).site_config_path
|
||||
|
||||
|
||||
def site_cache_path(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
opinion: bool = True, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> Path:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: cache directory tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
opinion=opinion,
|
||||
ensure_exists=ensure_exists,
|
||||
).site_cache_path
|
||||
|
||||
|
||||
def user_cache_path(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
opinion: bool = True, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> Path:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param opinion: See `roaming <platformdirs.api.PlatformDirsABC.opinion>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: cache path tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
opinion=opinion,
|
||||
ensure_exists=ensure_exists,
|
||||
).user_cache_path
|
||||
|
||||
|
||||
def user_state_path(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
roaming: bool = False, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> Path:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param roaming: See `roaming <platformdirs.api.PlatformDirsABC.roaming>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: state path tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
roaming=roaming,
|
||||
ensure_exists=ensure_exists,
|
||||
).user_state_path
|
||||
|
||||
|
||||
def user_log_path(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
opinion: bool = True, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> Path:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param opinion: See `roaming <platformdirs.api.PlatformDirsABC.opinion>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: log path tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
opinion=opinion,
|
||||
ensure_exists=ensure_exists,
|
||||
).user_log_path
|
||||
|
||||
|
||||
def user_documents_path() -> Path:
|
||||
""":returns: documents path tied to the user"""
|
||||
return PlatformDirs().user_documents_path
|
||||
|
||||
|
||||
def user_downloads_path() -> Path:
|
||||
""":returns: downloads path tied to the user"""
|
||||
return PlatformDirs().user_downloads_path
|
||||
|
||||
|
||||
def user_pictures_path() -> Path:
|
||||
""":returns: pictures path tied to the user"""
|
||||
return PlatformDirs().user_pictures_path
|
||||
|
||||
|
||||
def user_videos_path() -> Path:
|
||||
""":returns: videos path tied to the user"""
|
||||
return PlatformDirs().user_videos_path
|
||||
|
||||
|
||||
def user_music_path() -> Path:
|
||||
""":returns: music path tied to the user"""
|
||||
return PlatformDirs().user_music_path
|
||||
|
||||
|
||||
def user_runtime_path(
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
opinion: bool = True, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> Path:
|
||||
"""
|
||||
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
|
||||
:param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`.
|
||||
:param version: See `version <platformdirs.api.PlatformDirsABC.version>`.
|
||||
:param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
|
||||
:param ensure_exists: See `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
:returns: runtime path tied to the user
|
||||
"""
|
||||
return PlatformDirs(
|
||||
appname=appname,
|
||||
appauthor=appauthor,
|
||||
version=version,
|
||||
opinion=opinion,
|
||||
ensure_exists=ensure_exists,
|
||||
).user_runtime_path
|
||||
|
||||
|
||||
__all__ = [
|
||||
"__version__",
|
||||
"__version_info__",
|
||||
"PlatformDirs",
|
||||
"AppDirs",
|
||||
"PlatformDirsABC",
|
||||
"user_data_dir",
|
||||
"user_config_dir",
|
||||
"user_cache_dir",
|
||||
"user_state_dir",
|
||||
"user_log_dir",
|
||||
"user_documents_dir",
|
||||
"user_downloads_dir",
|
||||
"user_pictures_dir",
|
||||
"user_videos_dir",
|
||||
"user_music_dir",
|
||||
"user_runtime_dir",
|
||||
"site_data_dir",
|
||||
"site_config_dir",
|
||||
"site_cache_dir",
|
||||
"user_data_path",
|
||||
"user_config_path",
|
||||
"user_cache_path",
|
||||
"user_state_path",
|
||||
"user_log_path",
|
||||
"user_documents_path",
|
||||
"user_downloads_path",
|
||||
"user_pictures_path",
|
||||
"user_videos_path",
|
||||
"user_music_path",
|
||||
"user_runtime_path",
|
||||
"site_data_path",
|
||||
"site_config_path",
|
||||
"site_cache_path",
|
||||
]
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Main entry point."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pip._vendor.platformdirs import PlatformDirs, __version__
|
||||
|
||||
PROPS = (
|
||||
"user_data_dir",
|
||||
"user_config_dir",
|
||||
"user_cache_dir",
|
||||
"user_state_dir",
|
||||
"user_log_dir",
|
||||
"user_documents_dir",
|
||||
"user_downloads_dir",
|
||||
"user_pictures_dir",
|
||||
"user_videos_dir",
|
||||
"user_music_dir",
|
||||
"user_runtime_dir",
|
||||
"site_data_dir",
|
||||
"site_config_dir",
|
||||
"site_cache_dir",
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run main entry point."""
|
||||
app_name = "MyApp"
|
||||
app_author = "MyCompany"
|
||||
|
||||
print(f"-- platformdirs {__version__} --") # noqa: T201
|
||||
|
||||
print("-- app dirs (with optional 'version')") # noqa: T201
|
||||
dirs = PlatformDirs(app_name, app_author, version="1.0")
|
||||
for prop in PROPS:
|
||||
print(f"{prop}: {getattr(dirs, prop)}") # noqa: T201
|
||||
|
||||
print("\n-- app dirs (without optional 'version')") # noqa: T201
|
||||
dirs = PlatformDirs(app_name, app_author)
|
||||
for prop in PROPS:
|
||||
print(f"{prop}: {getattr(dirs, prop)}") # noqa: T201
|
||||
|
||||
print("\n-- app dirs (without optional 'appauthor')") # noqa: T201
|
||||
dirs = PlatformDirs(app_name)
|
||||
for prop in PROPS:
|
||||
print(f"{prop}: {getattr(dirs, prop)}") # noqa: T201
|
||||
|
||||
print("\n-- app dirs (with disabled 'appauthor')") # noqa: T201
|
||||
dirs = PlatformDirs(app_name, appauthor=False)
|
||||
for prop in PROPS:
|
||||
print(f"{prop}: {getattr(dirs, prop)}") # noqa: T201
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,210 @@
|
||||
"""Android."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from typing import cast
|
||||
|
||||
from .api import PlatformDirsABC
|
||||
|
||||
|
||||
class Android(PlatformDirsABC):
|
||||
"""
|
||||
Follows the guidance `from here <https://android.stackexchange.com/a/216132>`_. Makes use of the
|
||||
`appname <platformdirs.api.PlatformDirsABC.appname>`,
|
||||
`version <platformdirs.api.PlatformDirsABC.version>`,
|
||||
`ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
"""
|
||||
|
||||
@property
|
||||
def user_data_dir(self) -> str:
|
||||
""":return: data directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/files/<AppName>``"""
|
||||
return self._append_app_name_and_version(cast(str, _android_folder()), "files")
|
||||
|
||||
@property
|
||||
def site_data_dir(self) -> str:
|
||||
""":return: data directory shared by users, same as `user_data_dir`"""
|
||||
return self.user_data_dir
|
||||
|
||||
@property
|
||||
def user_config_dir(self) -> str:
|
||||
"""
|
||||
:return: config directory tied to the user, e.g. \
|
||||
``/data/user/<userid>/<packagename>/shared_prefs/<AppName>``
|
||||
"""
|
||||
return self._append_app_name_and_version(cast(str, _android_folder()), "shared_prefs")
|
||||
|
||||
@property
|
||||
def site_config_dir(self) -> str:
|
||||
""":return: config directory shared by the users, same as `user_config_dir`"""
|
||||
return self.user_config_dir
|
||||
|
||||
@property
|
||||
def user_cache_dir(self) -> str:
|
||||
""":return: cache directory tied to the user, e.g. e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>``"""
|
||||
return self._append_app_name_and_version(cast(str, _android_folder()), "cache")
|
||||
|
||||
@property
|
||||
def site_cache_dir(self) -> str:
|
||||
""":return: cache directory shared by users, same as `user_cache_dir`"""
|
||||
return self.user_cache_dir
|
||||
|
||||
@property
|
||||
def user_state_dir(self) -> str:
|
||||
""":return: state directory tied to the user, same as `user_data_dir`"""
|
||||
return self.user_data_dir
|
||||
|
||||
@property
|
||||
def user_log_dir(self) -> str:
|
||||
"""
|
||||
:return: log directory tied to the user, same as `user_cache_dir` if not opinionated else ``log`` in it,
|
||||
e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>/log``
|
||||
"""
|
||||
path = self.user_cache_dir
|
||||
if self.opinion:
|
||||
path = os.path.join(path, "log") # noqa: PTH118
|
||||
return path
|
||||
|
||||
@property
|
||||
def user_documents_dir(self) -> str:
|
||||
""":return: documents directory tied to the user e.g. ``/storage/emulated/0/Documents``"""
|
||||
return _android_documents_folder()
|
||||
|
||||
@property
|
||||
def user_downloads_dir(self) -> str:
|
||||
""":return: downloads directory tied to the user e.g. ``/storage/emulated/0/Downloads``"""
|
||||
return _android_downloads_folder()
|
||||
|
||||
@property
|
||||
def user_pictures_dir(self) -> str:
|
||||
""":return: pictures directory tied to the user e.g. ``/storage/emulated/0/Pictures``"""
|
||||
return _android_pictures_folder()
|
||||
|
||||
@property
|
||||
def user_videos_dir(self) -> str:
|
||||
""":return: videos directory tied to the user e.g. ``/storage/emulated/0/DCIM/Camera``"""
|
||||
return _android_videos_folder()
|
||||
|
||||
@property
|
||||
def user_music_dir(self) -> str:
|
||||
""":return: music directory tied to the user e.g. ``/storage/emulated/0/Music``"""
|
||||
return _android_music_folder()
|
||||
|
||||
@property
|
||||
def user_runtime_dir(self) -> str:
|
||||
"""
|
||||
:return: runtime directory tied to the user, same as `user_cache_dir` if not opinionated else ``tmp`` in it,
|
||||
e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>/tmp``
|
||||
"""
|
||||
path = self.user_cache_dir
|
||||
if self.opinion:
|
||||
path = os.path.join(path, "tmp") # noqa: PTH118
|
||||
return path
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _android_folder() -> str | None:
|
||||
""":return: base folder for the Android OS or None if it cannot be found"""
|
||||
try:
|
||||
# First try to get path to android app via pyjnius
|
||||
from jnius import autoclass
|
||||
|
||||
context = autoclass("android.content.Context")
|
||||
result: str | None = context.getFilesDir().getParentFile().getAbsolutePath()
|
||||
except Exception: # noqa: BLE001
|
||||
# if fails find an android folder looking path on the sys.path
|
||||
pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files")
|
||||
for path in sys.path:
|
||||
if pattern.match(path):
|
||||
result = path.split("/files")[0]
|
||||
break
|
||||
else:
|
||||
result = None
|
||||
return result
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _android_documents_folder() -> str:
|
||||
""":return: documents folder for the Android OS"""
|
||||
# Get directories with pyjnius
|
||||
try:
|
||||
from jnius import autoclass
|
||||
|
||||
context = autoclass("android.content.Context")
|
||||
environment = autoclass("android.os.Environment")
|
||||
documents_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DOCUMENTS).getAbsolutePath()
|
||||
except Exception: # noqa: BLE001
|
||||
documents_dir = "/storage/emulated/0/Documents"
|
||||
|
||||
return documents_dir
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _android_downloads_folder() -> str:
|
||||
""":return: downloads folder for the Android OS"""
|
||||
# Get directories with pyjnius
|
||||
try:
|
||||
from jnius import autoclass
|
||||
|
||||
context = autoclass("android.content.Context")
|
||||
environment = autoclass("android.os.Environment")
|
||||
downloads_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DOWNLOADS).getAbsolutePath()
|
||||
except Exception: # noqa: BLE001
|
||||
downloads_dir = "/storage/emulated/0/Downloads"
|
||||
|
||||
return downloads_dir
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _android_pictures_folder() -> str:
|
||||
""":return: pictures folder for the Android OS"""
|
||||
# Get directories with pyjnius
|
||||
try:
|
||||
from jnius import autoclass
|
||||
|
||||
context = autoclass("android.content.Context")
|
||||
environment = autoclass("android.os.Environment")
|
||||
pictures_dir: str = context.getExternalFilesDir(environment.DIRECTORY_PICTURES).getAbsolutePath()
|
||||
except Exception: # noqa: BLE001
|
||||
pictures_dir = "/storage/emulated/0/Pictures"
|
||||
|
||||
return pictures_dir
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _android_videos_folder() -> str:
|
||||
""":return: videos folder for the Android OS"""
|
||||
# Get directories with pyjnius
|
||||
try:
|
||||
from jnius import autoclass
|
||||
|
||||
context = autoclass("android.content.Context")
|
||||
environment = autoclass("android.os.Environment")
|
||||
videos_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DCIM).getAbsolutePath()
|
||||
except Exception: # noqa: BLE001
|
||||
videos_dir = "/storage/emulated/0/DCIM/Camera"
|
||||
|
||||
return videos_dir
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _android_music_folder() -> str:
|
||||
""":return: music folder for the Android OS"""
|
||||
# Get directories with pyjnius
|
||||
try:
|
||||
from jnius import autoclass
|
||||
|
||||
context = autoclass("android.content.Context")
|
||||
environment = autoclass("android.os.Environment")
|
||||
music_dir: str = context.getExternalFilesDir(environment.DIRECTORY_MUSIC).getAbsolutePath()
|
||||
except Exception: # noqa: BLE001
|
||||
music_dir = "/storage/emulated/0/Music"
|
||||
|
||||
return music_dir
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Android",
|
||||
]
|
||||
@@ -0,0 +1,223 @@
|
||||
"""Base API."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 8): # pragma: no cover (py38+)
|
||||
from typing import Literal
|
||||
else: # pragma: no cover (py38+)
|
||||
from pip._vendor.typing_extensions import Literal
|
||||
|
||||
|
||||
class PlatformDirsABC(ABC):
|
||||
"""Abstract base class for platform directories."""
|
||||
|
||||
def __init__( # noqa: PLR0913
|
||||
self,
|
||||
appname: str | None = None,
|
||||
appauthor: str | None | Literal[False] = None,
|
||||
version: str | None = None,
|
||||
roaming: bool = False, # noqa: FBT001, FBT002
|
||||
multipath: bool = False, # noqa: FBT001, FBT002
|
||||
opinion: bool = True, # noqa: FBT001, FBT002
|
||||
ensure_exists: bool = False, # noqa: FBT001, FBT002
|
||||
) -> None:
|
||||
"""
|
||||
Create a new platform directory.
|
||||
|
||||
:param appname: See `appname`.
|
||||
:param appauthor: See `appauthor`.
|
||||
:param version: See `version`.
|
||||
:param roaming: See `roaming`.
|
||||
:param multipath: See `multipath`.
|
||||
:param opinion: See `opinion`.
|
||||
:param ensure_exists: See `ensure_exists`.
|
||||
"""
|
||||
self.appname = appname #: The name of application.
|
||||
self.appauthor = appauthor
|
||||
"""
|
||||
The name of the app author or distributing body for this application. Typically, it is the owning company name.
|
||||
Defaults to `appname`. You may pass ``False`` to disable it.
|
||||
"""
|
||||
self.version = version
|
||||
"""
|
||||
An optional version path element to append to the path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this would typically be ``<major>.<minor>``.
|
||||
"""
|
||||
self.roaming = roaming
|
||||
"""
|
||||
Whether to use the roaming appdata directory on Windows. That means that for users on a Windows network setup
|
||||
for roaming profiles, this user data will be synced on login (see
|
||||
`here <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>`_).
|
||||
"""
|
||||
self.multipath = multipath
|
||||
"""
|
||||
An optional parameter only applicable to Unix/Linux which indicates that the entire list of data dirs should be
|
||||
returned. By default, the first item would only be returned.
|
||||
"""
|
||||
self.opinion = opinion #: A flag to indicating to use opinionated values.
|
||||
self.ensure_exists = ensure_exists
|
||||
"""
|
||||
Optionally create the directory (and any missing parents) upon access if it does not exist.
|
||||
By default, no directories are created.
|
||||
"""
|
||||
|
||||
def _append_app_name_and_version(self, *base: str) -> str:
|
||||
params = list(base[1:])
|
||||
if self.appname:
|
||||
params.append(self.appname)
|
||||
if self.version:
|
||||
params.append(self.version)
|
||||
path = os.path.join(base[0], *params) # noqa: PTH118
|
||||
self._optionally_create_directory(path)
|
||||
return path
|
||||
|
||||
def _optionally_create_directory(self, path: str) -> None:
|
||||
if self.ensure_exists:
|
||||
Path(path).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def user_data_dir(self) -> str:
|
||||
""":return: data directory tied to the user"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def site_data_dir(self) -> str:
|
||||
""":return: data directory shared by users"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def user_config_dir(self) -> str:
|
||||
""":return: config directory tied to the user"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def site_config_dir(self) -> str:
|
||||
""":return: config directory shared by the users"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def user_cache_dir(self) -> str:
|
||||
""":return: cache directory tied to the user"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def site_cache_dir(self) -> str:
|
||||
""":return: cache directory shared by users"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def user_state_dir(self) -> str:
|
||||
""":return: state directory tied to the user"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def user_log_dir(self) -> str:
|
||||
""":return: log directory tied to the user"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def user_documents_dir(self) -> str:
|
||||
""":return: documents directory tied to the user"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def user_downloads_dir(self) -> str:
|
||||
""":return: downloads directory tied to the user"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def user_pictures_dir(self) -> str:
|
||||
""":return: pictures directory tied to the user"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def user_videos_dir(self) -> str:
|
||||
""":return: videos directory tied to the user"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def user_music_dir(self) -> str:
|
||||
""":return: music directory tied to the user"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def user_runtime_dir(self) -> str:
|
||||
""":return: runtime directory tied to the user"""
|
||||
|
||||
@property
|
||||
def user_data_path(self) -> Path:
|
||||
""":return: data path tied to the user"""
|
||||
return Path(self.user_data_dir)
|
||||
|
||||
@property
|
||||
def site_data_path(self) -> Path:
|
||||
""":return: data path shared by users"""
|
||||
return Path(self.site_data_dir)
|
||||
|
||||
@property
|
||||
def user_config_path(self) -> Path:
|
||||
""":return: config path tied to the user"""
|
||||
return Path(self.user_config_dir)
|
||||
|
||||
@property
|
||||
def site_config_path(self) -> Path:
|
||||
""":return: config path shared by the users"""
|
||||
return Path(self.site_config_dir)
|
||||
|
||||
@property
|
||||
def user_cache_path(self) -> Path:
|
||||
""":return: cache path tied to the user"""
|
||||
return Path(self.user_cache_dir)
|
||||
|
||||
@property
|
||||
def site_cache_path(self) -> Path:
|
||||
""":return: cache path shared by users"""
|
||||
return Path(self.site_cache_dir)
|
||||
|
||||
@property
|
||||
def user_state_path(self) -> Path:
|
||||
""":return: state path tied to the user"""
|
||||
return Path(self.user_state_dir)
|
||||
|
||||
@property
|
||||
def user_log_path(self) -> Path:
|
||||
""":return: log path tied to the user"""
|
||||
return Path(self.user_log_dir)
|
||||
|
||||
@property
|
||||
def user_documents_path(self) -> Path:
|
||||
""":return: documents path tied to the user"""
|
||||
return Path(self.user_documents_dir)
|
||||
|
||||
@property
|
||||
def user_downloads_path(self) -> Path:
|
||||
""":return: downloads path tied to the user"""
|
||||
return Path(self.user_downloads_dir)
|
||||
|
||||
@property
|
||||
def user_pictures_path(self) -> Path:
|
||||
""":return: pictures path tied to the user"""
|
||||
return Path(self.user_pictures_dir)
|
||||
|
||||
@property
|
||||
def user_videos_path(self) -> Path:
|
||||
""":return: videos path tied to the user"""
|
||||
return Path(self.user_videos_dir)
|
||||
|
||||
@property
|
||||
def user_music_path(self) -> Path:
|
||||
""":return: music path tied to the user"""
|
||||
return Path(self.user_music_dir)
|
||||
|
||||
@property
|
||||
def user_runtime_path(self) -> Path:
|
||||
""":return: runtime path tied to the user"""
|
||||
return Path(self.user_runtime_dir)
|
||||
@@ -0,0 +1,91 @@
|
||||
"""macOS."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
|
||||
from .api import PlatformDirsABC
|
||||
|
||||
|
||||
class MacOS(PlatformDirsABC):
|
||||
"""
|
||||
Platform directories for the macOS operating system. Follows the guidance from `Apple documentation
|
||||
<https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html>`_.
|
||||
Makes use of the `appname <platformdirs.api.PlatformDirsABC.appname>`,
|
||||
`version <platformdirs.api.PlatformDirsABC.version>`,
|
||||
`ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
"""
|
||||
|
||||
@property
|
||||
def user_data_dir(self) -> str:
|
||||
""":return: data directory tied to the user, e.g. ``~/Library/Application Support/$appname/$version``"""
|
||||
return self._append_app_name_and_version(os.path.expanduser("~/Library/Application Support")) # noqa: PTH111
|
||||
|
||||
@property
|
||||
def site_data_dir(self) -> str:
|
||||
""":return: data directory shared by users, e.g. ``/Library/Application Support/$appname/$version``"""
|
||||
return self._append_app_name_and_version("/Library/Application Support")
|
||||
|
||||
@property
|
||||
def user_config_dir(self) -> str:
|
||||
""":return: config directory tied to the user, same as `user_data_dir`"""
|
||||
return self.user_data_dir
|
||||
|
||||
@property
|
||||
def site_config_dir(self) -> str:
|
||||
""":return: config directory shared by the users, same as `site_data_dir`"""
|
||||
return self.site_data_dir
|
||||
|
||||
@property
|
||||
def user_cache_dir(self) -> str:
|
||||
""":return: cache directory tied to the user, e.g. ``~/Library/Caches/$appname/$version``"""
|
||||
return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches")) # noqa: PTH111
|
||||
|
||||
@property
|
||||
def site_cache_dir(self) -> str:
|
||||
""":return: cache directory shared by users, e.g. ``/Library/Caches/$appname/$version``"""
|
||||
return self._append_app_name_and_version("/Library/Caches")
|
||||
|
||||
@property
|
||||
def user_state_dir(self) -> str:
|
||||
""":return: state directory tied to the user, same as `user_data_dir`"""
|
||||
return self.user_data_dir
|
||||
|
||||
@property
|
||||
def user_log_dir(self) -> str:
|
||||
""":return: log directory tied to the user, e.g. ``~/Library/Logs/$appname/$version``"""
|
||||
return self._append_app_name_and_version(os.path.expanduser("~/Library/Logs")) # noqa: PTH111
|
||||
|
||||
@property
|
||||
def user_documents_dir(self) -> str:
|
||||
""":return: documents directory tied to the user, e.g. ``~/Documents``"""
|
||||
return os.path.expanduser("~/Documents") # noqa: PTH111
|
||||
|
||||
@property
|
||||
def user_downloads_dir(self) -> str:
|
||||
""":return: downloads directory tied to the user, e.g. ``~/Downloads``"""
|
||||
return os.path.expanduser("~/Downloads") # noqa: PTH111
|
||||
|
||||
@property
|
||||
def user_pictures_dir(self) -> str:
|
||||
""":return: pictures directory tied to the user, e.g. ``~/Pictures``"""
|
||||
return os.path.expanduser("~/Pictures") # noqa: PTH111
|
||||
|
||||
@property
|
||||
def user_videos_dir(self) -> str:
|
||||
""":return: videos directory tied to the user, e.g. ``~/Movies``"""
|
||||
return os.path.expanduser("~/Movies") # noqa: PTH111
|
||||
|
||||
@property
|
||||
def user_music_dir(self) -> str:
|
||||
""":return: music directory tied to the user, e.g. ``~/Music``"""
|
||||
return os.path.expanduser("~/Music") # noqa: PTH111
|
||||
|
||||
@property
|
||||
def user_runtime_dir(self) -> str:
|
||||
""":return: runtime directory tied to the user, e.g. ``~/Library/Caches/TemporaryItems/$appname/$version``"""
|
||||
return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches/TemporaryItems")) # noqa: PTH111
|
||||
|
||||
|
||||
__all__ = [
|
||||
"MacOS",
|
||||
]
|
||||
@@ -0,0 +1,223 @@
|
||||
"""Unix."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from configparser import ConfigParser
|
||||
from pathlib import Path
|
||||
|
||||
from .api import PlatformDirsABC
|
||||
|
||||
if sys.platform == "win32":
|
||||
|
||||
def getuid() -> int:
|
||||
msg = "should only be used on Unix"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
else:
|
||||
from os import getuid
|
||||
|
||||
|
||||
class Unix(PlatformDirsABC):
|
||||
"""
|
||||
On Unix/Linux, we follow the
|
||||
`XDG Basedir Spec <https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_. The spec allows
|
||||
overriding directories with environment variables. The examples show are the default values, alongside the name of
|
||||
the environment variable that overrides them. Makes use of the
|
||||
`appname <platformdirs.api.PlatformDirsABC.appname>`,
|
||||
`version <platformdirs.api.PlatformDirsABC.version>`,
|
||||
`multipath <platformdirs.api.PlatformDirsABC.multipath>`,
|
||||
`opinion <platformdirs.api.PlatformDirsABC.opinion>`,
|
||||
`ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
"""
|
||||
|
||||
@property
|
||||
def user_data_dir(self) -> str:
|
||||
"""
|
||||
:return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or
|
||||
``$XDG_DATA_HOME/$appname/$version``
|
||||
"""
|
||||
path = os.environ.get("XDG_DATA_HOME", "")
|
||||
if not path.strip():
|
||||
path = os.path.expanduser("~/.local/share") # noqa: PTH111
|
||||
return self._append_app_name_and_version(path)
|
||||
|
||||
@property
|
||||
def site_data_dir(self) -> str:
|
||||
"""
|
||||
:return: data directories shared by users (if `multipath <platformdirs.api.PlatformDirsABC.multipath>` is
|
||||
enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS
|
||||
path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version``
|
||||
"""
|
||||
# XDG default for $XDG_DATA_DIRS; only first, if multipath is False
|
||||
path = os.environ.get("XDG_DATA_DIRS", "")
|
||||
if not path.strip():
|
||||
path = f"/usr/local/share{os.pathsep}/usr/share"
|
||||
return self._with_multi_path(path)
|
||||
|
||||
def _with_multi_path(self, path: str) -> str:
|
||||
path_list = path.split(os.pathsep)
|
||||
if not self.multipath:
|
||||
path_list = path_list[0:1]
|
||||
path_list = [self._append_app_name_and_version(os.path.expanduser(p)) for p in path_list] # noqa: PTH111
|
||||
return os.pathsep.join(path_list)
|
||||
|
||||
@property
|
||||
def user_config_dir(self) -> str:
|
||||
"""
|
||||
:return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or
|
||||
``$XDG_CONFIG_HOME/$appname/$version``
|
||||
"""
|
||||
path = os.environ.get("XDG_CONFIG_HOME", "")
|
||||
if not path.strip():
|
||||
path = os.path.expanduser("~/.config") # noqa: PTH111
|
||||
return self._append_app_name_and_version(path)
|
||||
|
||||
@property
|
||||
def site_config_dir(self) -> str:
|
||||
"""
|
||||
:return: config directories shared by users (if `multipath <platformdirs.api.PlatformDirsABC.multipath>`
|
||||
is enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS
|
||||
path separator), e.g. ``/etc/xdg/$appname/$version``
|
||||
"""
|
||||
# XDG default for $XDG_CONFIG_DIRS only first, if multipath is False
|
||||
path = os.environ.get("XDG_CONFIG_DIRS", "")
|
||||
if not path.strip():
|
||||
path = "/etc/xdg"
|
||||
return self._with_multi_path(path)
|
||||
|
||||
@property
|
||||
def user_cache_dir(self) -> str:
|
||||
"""
|
||||
:return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or
|
||||
``~/$XDG_CACHE_HOME/$appname/$version``
|
||||
"""
|
||||
path = os.environ.get("XDG_CACHE_HOME", "")
|
||||
if not path.strip():
|
||||
path = os.path.expanduser("~/.cache") # noqa: PTH111
|
||||
return self._append_app_name_and_version(path)
|
||||
|
||||
@property
|
||||
def site_cache_dir(self) -> str:
|
||||
""":return: cache directory shared by users, e.g. ``/var/tmp/$appname/$version``"""
|
||||
return self._append_app_name_and_version("/var/tmp") # noqa: S108
|
||||
|
||||
@property
|
||||
def user_state_dir(self) -> str:
|
||||
"""
|
||||
:return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or
|
||||
``$XDG_STATE_HOME/$appname/$version``
|
||||
"""
|
||||
path = os.environ.get("XDG_STATE_HOME", "")
|
||||
if not path.strip():
|
||||
path = os.path.expanduser("~/.local/state") # noqa: PTH111
|
||||
return self._append_app_name_and_version(path)
|
||||
|
||||
@property
|
||||
def user_log_dir(self) -> str:
|
||||
""":return: log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it"""
|
||||
path = self.user_state_dir
|
||||
if self.opinion:
|
||||
path = os.path.join(path, "log") # noqa: PTH118
|
||||
return path
|
||||
|
||||
@property
|
||||
def user_documents_dir(self) -> str:
|
||||
""":return: documents directory tied to the user, e.g. ``~/Documents``"""
|
||||
return _get_user_media_dir("XDG_DOCUMENTS_DIR", "~/Documents")
|
||||
|
||||
@property
|
||||
def user_downloads_dir(self) -> str:
|
||||
""":return: downloads directory tied to the user, e.g. ``~/Downloads``"""
|
||||
return _get_user_media_dir("XDG_DOWNLOAD_DIR", "~/Downloads")
|
||||
|
||||
@property
|
||||
def user_pictures_dir(self) -> str:
|
||||
""":return: pictures directory tied to the user, e.g. ``~/Pictures``"""
|
||||
return _get_user_media_dir("XDG_PICTURES_DIR", "~/Pictures")
|
||||
|
||||
@property
|
||||
def user_videos_dir(self) -> str:
|
||||
""":return: videos directory tied to the user, e.g. ``~/Videos``"""
|
||||
return _get_user_media_dir("XDG_VIDEOS_DIR", "~/Videos")
|
||||
|
||||
@property
|
||||
def user_music_dir(self) -> str:
|
||||
""":return: music directory tied to the user, e.g. ``~/Music``"""
|
||||
return _get_user_media_dir("XDG_MUSIC_DIR", "~/Music")
|
||||
|
||||
@property
|
||||
def user_runtime_dir(self) -> str:
|
||||
"""
|
||||
:return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or
|
||||
``$XDG_RUNTIME_DIR/$appname/$version``.
|
||||
|
||||
For FreeBSD/OpenBSD/NetBSD, it would return ``/var/run/user/$(id -u)/$appname/$version`` if
|
||||
exists, otherwise ``/tmp/runtime-$(id -u)/$appname/$version``, if``$XDG_RUNTIME_DIR``
|
||||
is not set.
|
||||
"""
|
||||
path = os.environ.get("XDG_RUNTIME_DIR", "")
|
||||
if not path.strip():
|
||||
if sys.platform.startswith(("freebsd", "openbsd", "netbsd")):
|
||||
path = f"/var/run/user/{getuid()}"
|
||||
if not Path(path).exists():
|
||||
path = f"/tmp/runtime-{getuid()}" # noqa: S108
|
||||
else:
|
||||
path = f"/run/user/{getuid()}"
|
||||
return self._append_app_name_and_version(path)
|
||||
|
||||
@property
|
||||
def site_data_path(self) -> Path:
|
||||
""":return: data path shared by users. Only return first item, even if ``multipath`` is set to ``True``"""
|
||||
return self._first_item_as_path_if_multipath(self.site_data_dir)
|
||||
|
||||
@property
|
||||
def site_config_path(self) -> Path:
|
||||
""":return: config path shared by the users. Only return first item, even if ``multipath`` is set to ``True``"""
|
||||
return self._first_item_as_path_if_multipath(self.site_config_dir)
|
||||
|
||||
@property
|
||||
def site_cache_path(self) -> Path:
|
||||
""":return: cache path shared by users. Only return first item, even if ``multipath`` is set to ``True``"""
|
||||
return self._first_item_as_path_if_multipath(self.site_cache_dir)
|
||||
|
||||
def _first_item_as_path_if_multipath(self, directory: str) -> Path:
|
||||
if self.multipath:
|
||||
# If multipath is True, the first path is returned.
|
||||
directory = directory.split(os.pathsep)[0]
|
||||
return Path(directory)
|
||||
|
||||
|
||||
def _get_user_media_dir(env_var: str, fallback_tilde_path: str) -> str:
|
||||
media_dir = _get_user_dirs_folder(env_var)
|
||||
if media_dir is None:
|
||||
media_dir = os.environ.get(env_var, "").strip()
|
||||
if not media_dir:
|
||||
media_dir = os.path.expanduser(fallback_tilde_path) # noqa: PTH111
|
||||
|
||||
return media_dir
|
||||
|
||||
|
||||
def _get_user_dirs_folder(key: str) -> str | None:
|
||||
"""Return directory from user-dirs.dirs config file. See https://freedesktop.org/wiki/Software/xdg-user-dirs/."""
|
||||
user_dirs_config_path = Path(Unix().user_config_dir) / "user-dirs.dirs"
|
||||
if user_dirs_config_path.exists():
|
||||
parser = ConfigParser()
|
||||
|
||||
with user_dirs_config_path.open() as stream:
|
||||
# Add fake section header, so ConfigParser doesn't complain
|
||||
parser.read_string(f"[top]\n{stream.read()}")
|
||||
|
||||
if key not in parser["top"]:
|
||||
return None
|
||||
|
||||
path = parser["top"][key].strip('"')
|
||||
# Handle relative home paths
|
||||
return path.replace("$HOME", os.path.expanduser("~")) # noqa: PTH111
|
||||
|
||||
return None
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Unix",
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
# file generated by setuptools_scm
|
||||
# don't change, don't track in version control
|
||||
__version__ = version = '3.8.1'
|
||||
__version_tuple__ = version_tuple = (3, 8, 1)
|
||||
@@ -0,0 +1,255 @@
|
||||
"""Windows."""
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
import os
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .api import PlatformDirsABC
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
class Windows(PlatformDirsABC):
|
||||
"""
|
||||
`MSDN on where to store app data files
|
||||
<http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120>`_.
|
||||
Makes use of the
|
||||
`appname <platformdirs.api.PlatformDirsABC.appname>`,
|
||||
`appauthor <platformdirs.api.PlatformDirsABC.appauthor>`,
|
||||
`version <platformdirs.api.PlatformDirsABC.version>`,
|
||||
`roaming <platformdirs.api.PlatformDirsABC.roaming>`,
|
||||
`opinion <platformdirs.api.PlatformDirsABC.opinion>`,
|
||||
`ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
|
||||
"""
|
||||
|
||||
@property
|
||||
def user_data_dir(self) -> str:
|
||||
"""
|
||||
:return: data directory tied to the user, e.g.
|
||||
``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname`` (not roaming) or
|
||||
``%USERPROFILE%\\AppData\\Roaming\\$appauthor\\$appname`` (roaming)
|
||||
"""
|
||||
const = "CSIDL_APPDATA" if self.roaming else "CSIDL_LOCAL_APPDATA"
|
||||
path = os.path.normpath(get_win_folder(const))
|
||||
return self._append_parts(path)
|
||||
|
||||
def _append_parts(self, path: str, *, opinion_value: str | None = None) -> str:
|
||||
params = []
|
||||
if self.appname:
|
||||
if self.appauthor is not False:
|
||||
author = self.appauthor or self.appname
|
||||
params.append(author)
|
||||
params.append(self.appname)
|
||||
if opinion_value is not None and self.opinion:
|
||||
params.append(opinion_value)
|
||||
if self.version:
|
||||
params.append(self.version)
|
||||
path = os.path.join(path, *params) # noqa: PTH118
|
||||
self._optionally_create_directory(path)
|
||||
return path
|
||||
|
||||
@property
|
||||
def site_data_dir(self) -> str:
|
||||
""":return: data directory shared by users, e.g. ``C:\\ProgramData\\$appauthor\\$appname``"""
|
||||
path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA"))
|
||||
return self._append_parts(path)
|
||||
|
||||
@property
|
||||
def user_config_dir(self) -> str:
|
||||
""":return: config directory tied to the user, same as `user_data_dir`"""
|
||||
return self.user_data_dir
|
||||
|
||||
@property
|
||||
def site_config_dir(self) -> str:
|
||||
""":return: config directory shared by the users, same as `site_data_dir`"""
|
||||
return self.site_data_dir
|
||||
|
||||
@property
|
||||
def user_cache_dir(self) -> str:
|
||||
"""
|
||||
:return: cache directory tied to the user (if opinionated with ``Cache`` folder within ``$appname``) e.g.
|
||||
``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname\\Cache\\$version``
|
||||
"""
|
||||
path = os.path.normpath(get_win_folder("CSIDL_LOCAL_APPDATA"))
|
||||
return self._append_parts(path, opinion_value="Cache")
|
||||
|
||||
@property
|
||||
def site_cache_dir(self) -> str:
|
||||
""":return: cache directory shared by users, e.g. ``C:\\ProgramData\\$appauthor\\$appname\\Cache\\$version``"""
|
||||
path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA"))
|
||||
return self._append_parts(path, opinion_value="Cache")
|
||||
|
||||
@property
|
||||
def user_state_dir(self) -> str:
|
||||
""":return: state directory tied to the user, same as `user_data_dir`"""
|
||||
return self.user_data_dir
|
||||
|
||||
@property
|
||||
def user_log_dir(self) -> str:
|
||||
""":return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``Logs`` in it"""
|
||||
path = self.user_data_dir
|
||||
if self.opinion:
|
||||
path = os.path.join(path, "Logs") # noqa: PTH118
|
||||
self._optionally_create_directory(path)
|
||||
return path
|
||||
|
||||
@property
|
||||
def user_documents_dir(self) -> str:
|
||||
""":return: documents directory tied to the user e.g. ``%USERPROFILE%\\Documents``"""
|
||||
return os.path.normpath(get_win_folder("CSIDL_PERSONAL"))
|
||||
|
||||
@property
|
||||
def user_downloads_dir(self) -> str:
|
||||
""":return: downloads directory tied to the user e.g. ``%USERPROFILE%\\Downloads``"""
|
||||
return os.path.normpath(get_win_folder("CSIDL_DOWNLOADS"))
|
||||
|
||||
@property
|
||||
def user_pictures_dir(self) -> str:
|
||||
""":return: pictures directory tied to the user e.g. ``%USERPROFILE%\\Pictures``"""
|
||||
return os.path.normpath(get_win_folder("CSIDL_MYPICTURES"))
|
||||
|
||||
@property
|
||||
def user_videos_dir(self) -> str:
|
||||
""":return: videos directory tied to the user e.g. ``%USERPROFILE%\\Videos``"""
|
||||
return os.path.normpath(get_win_folder("CSIDL_MYVIDEO"))
|
||||
|
||||
@property
|
||||
def user_music_dir(self) -> str:
|
||||
""":return: music directory tied to the user e.g. ``%USERPROFILE%\\Music``"""
|
||||
return os.path.normpath(get_win_folder("CSIDL_MYMUSIC"))
|
||||
|
||||
@property
|
||||
def user_runtime_dir(self) -> str:
|
||||
"""
|
||||
:return: runtime directory tied to the user, e.g.
|
||||
``%USERPROFILE%\\AppData\\Local\\Temp\\$appauthor\\$appname``
|
||||
"""
|
||||
path = os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Temp")) # noqa: PTH118
|
||||
return self._append_parts(path)
|
||||
|
||||
|
||||
def get_win_folder_from_env_vars(csidl_name: str) -> str:
|
||||
"""Get folder from environment variables."""
|
||||
result = get_win_folder_if_csidl_name_not_env_var(csidl_name)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
env_var_name = {
|
||||
"CSIDL_APPDATA": "APPDATA",
|
||||
"CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE",
|
||||
"CSIDL_LOCAL_APPDATA": "LOCALAPPDATA",
|
||||
}.get(csidl_name)
|
||||
if env_var_name is None:
|
||||
msg = f"Unknown CSIDL name: {csidl_name}"
|
||||
raise ValueError(msg)
|
||||
result = os.environ.get(env_var_name)
|
||||
if result is None:
|
||||
msg = f"Unset environment variable: {env_var_name}"
|
||||
raise ValueError(msg)
|
||||
return result
|
||||
|
||||
|
||||
def get_win_folder_if_csidl_name_not_env_var(csidl_name: str) -> str | None:
|
||||
"""Get folder for a CSIDL name that does not exist as an environment variable."""
|
||||
if csidl_name == "CSIDL_PERSONAL":
|
||||
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents") # noqa: PTH118
|
||||
|
||||
if csidl_name == "CSIDL_DOWNLOADS":
|
||||
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Downloads") # noqa: PTH118
|
||||
|
||||
if csidl_name == "CSIDL_MYPICTURES":
|
||||
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Pictures") # noqa: PTH118
|
||||
|
||||
if csidl_name == "CSIDL_MYVIDEO":
|
||||
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Videos") # noqa: PTH118
|
||||
|
||||
if csidl_name == "CSIDL_MYMUSIC":
|
||||
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Music") # noqa: PTH118
|
||||
return None
|
||||
|
||||
|
||||
def get_win_folder_from_registry(csidl_name: str) -> str:
|
||||
"""
|
||||
Get folder from the registry.
|
||||
|
||||
This is a fallback technique at best. I'm not sure if using the registry for these guarantees us the correct answer
|
||||
for all CSIDL_* names.
|
||||
"""
|
||||
shell_folder_name = {
|
||||
"CSIDL_APPDATA": "AppData",
|
||||
"CSIDL_COMMON_APPDATA": "Common AppData",
|
||||
"CSIDL_LOCAL_APPDATA": "Local AppData",
|
||||
"CSIDL_PERSONAL": "Personal",
|
||||
"CSIDL_DOWNLOADS": "{374DE290-123F-4565-9164-39C4925E467B}",
|
||||
"CSIDL_MYPICTURES": "My Pictures",
|
||||
"CSIDL_MYVIDEO": "My Video",
|
||||
"CSIDL_MYMUSIC": "My Music",
|
||||
}.get(csidl_name)
|
||||
if shell_folder_name is None:
|
||||
msg = f"Unknown CSIDL name: {csidl_name}"
|
||||
raise ValueError(msg)
|
||||
if sys.platform != "win32": # only needed for mypy type checker to know that this code runs only on Windows
|
||||
raise NotImplementedError
|
||||
import winreg
|
||||
|
||||
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
|
||||
directory, _ = winreg.QueryValueEx(key, shell_folder_name)
|
||||
return str(directory)
|
||||
|
||||
|
||||
def get_win_folder_via_ctypes(csidl_name: str) -> str:
|
||||
"""Get folder with ctypes."""
|
||||
# There is no 'CSIDL_DOWNLOADS'.
|
||||
# Use 'CSIDL_PROFILE' (40) and append the default folder 'Downloads' instead.
|
||||
# https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
|
||||
|
||||
csidl_const = {
|
||||
"CSIDL_APPDATA": 26,
|
||||
"CSIDL_COMMON_APPDATA": 35,
|
||||
"CSIDL_LOCAL_APPDATA": 28,
|
||||
"CSIDL_PERSONAL": 5,
|
||||
"CSIDL_MYPICTURES": 39,
|
||||
"CSIDL_MYVIDEO": 14,
|
||||
"CSIDL_MYMUSIC": 13,
|
||||
"CSIDL_DOWNLOADS": 40,
|
||||
}.get(csidl_name)
|
||||
if csidl_const is None:
|
||||
msg = f"Unknown CSIDL name: {csidl_name}"
|
||||
raise ValueError(msg)
|
||||
|
||||
buf = ctypes.create_unicode_buffer(1024)
|
||||
windll = getattr(ctypes, "windll") # noqa: B009 # using getattr to avoid false positive with mypy type checker
|
||||
windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
|
||||
|
||||
# Downgrade to short path name if it has highbit chars.
|
||||
if any(ord(c) > 255 for c in buf): # noqa: PLR2004
|
||||
buf2 = ctypes.create_unicode_buffer(1024)
|
||||
if windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
|
||||
buf = buf2
|
||||
|
||||
if csidl_name == "CSIDL_DOWNLOADS":
|
||||
return os.path.join(buf.value, "Downloads") # noqa: PTH118
|
||||
|
||||
return buf.value
|
||||
|
||||
|
||||
def _pick_get_win_folder() -> Callable[[str], str]:
|
||||
if hasattr(ctypes, "windll"):
|
||||
return get_win_folder_via_ctypes
|
||||
try:
|
||||
import winreg # noqa: F401
|
||||
except ImportError:
|
||||
return get_win_folder_from_env_vars
|
||||
else:
|
||||
return get_win_folder_from_registry
|
||||
|
||||
|
||||
get_win_folder = lru_cache(maxsize=None)(_pick_get_win_folder())
|
||||
|
||||
__all__ = [
|
||||
"Windows",
|
||||
]
|
||||
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
Pygments
|
||||
~~~~~~~~
|
||||
|
||||
Pygments is a syntax highlighting package written in Python.
|
||||
|
||||
It is a generic syntax highlighter for general use in all kinds of software
|
||||
such as forum systems, wikis or other applications that need to prettify
|
||||
source code. Highlights are:
|
||||
|
||||
* a wide range of common languages and markup formats is supported
|
||||
* special attention is paid to details, increasing quality by a fair amount
|
||||
* support for new languages and formats are added easily
|
||||
* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image
|
||||
formats that PIL supports, and ANSI sequences
|
||||
* it is usable as a command-line tool and as a library
|
||||
* ... and it highlights even Brainfuck!
|
||||
|
||||
The `Pygments master branch`_ is installable with ``easy_install Pygments==dev``.
|
||||
|
||||
.. _Pygments master branch:
|
||||
https://github.com/pygments/pygments/archive/master.zip#egg=Pygments-dev
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
from io import StringIO, BytesIO
|
||||
|
||||
__version__ = '2.15.1'
|
||||
__docformat__ = 'restructuredtext'
|
||||
|
||||
__all__ = ['lex', 'format', 'highlight']
|
||||
|
||||
|
||||
def lex(code, lexer):
|
||||
"""
|
||||
Lex `code` with the `lexer` (must be a `Lexer` instance)
|
||||
and return an iterable of tokens. Currently, this only calls
|
||||
`lexer.get_tokens()`.
|
||||
"""
|
||||
try:
|
||||
return lexer.get_tokens(code)
|
||||
except TypeError:
|
||||
# Heuristic to catch a common mistake.
|
||||
from pip._vendor.pygments.lexer import RegexLexer
|
||||
if isinstance(lexer, type) and issubclass(lexer, RegexLexer):
|
||||
raise TypeError('lex() argument must be a lexer instance, '
|
||||
'not a class')
|
||||
raise
|
||||
|
||||
|
||||
def format(tokens, formatter, outfile=None): # pylint: disable=redefined-builtin
|
||||
"""
|
||||
Format ``tokens`` (an iterable of tokens) with the formatter ``formatter``
|
||||
(a `Formatter` instance).
|
||||
|
||||
If ``outfile`` is given and a valid file object (an object with a
|
||||
``write`` method), the result will be written to it, otherwise it
|
||||
is returned as a string.
|
||||
"""
|
||||
try:
|
||||
if not outfile:
|
||||
realoutfile = getattr(formatter, 'encoding', None) and BytesIO() or StringIO()
|
||||
formatter.format(tokens, realoutfile)
|
||||
return realoutfile.getvalue()
|
||||
else:
|
||||
formatter.format(tokens, outfile)
|
||||
except TypeError:
|
||||
# Heuristic to catch a common mistake.
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
if isinstance(formatter, type) and issubclass(formatter, Formatter):
|
||||
raise TypeError('format() argument must be a formatter instance, '
|
||||
'not a class')
|
||||
raise
|
||||
|
||||
|
||||
def highlight(code, lexer, formatter, outfile=None):
|
||||
"""
|
||||
This is the most high-level highlighting function. It combines `lex` and
|
||||
`format` in one function.
|
||||
"""
|
||||
return format(lex(code, lexer), formatter, outfile)
|
||||
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
pygments.__main__
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Main entry point for ``python -m pygments``.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pip._vendor.pygments.cmdline import main
|
||||
|
||||
try:
|
||||
sys.exit(main(sys.argv))
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(1)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,668 @@
|
||||
"""
|
||||
pygments.cmdline
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Command line interface.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import argparse
|
||||
from textwrap import dedent
|
||||
|
||||
from pip._vendor.pygments import __version__, highlight
|
||||
from pip._vendor.pygments.util import ClassNotFound, OptionError, docstring_headline, \
|
||||
guess_decode, guess_decode_from_terminal, terminal_encoding, \
|
||||
UnclosingTextIOWrapper
|
||||
from pip._vendor.pygments.lexers import get_all_lexers, get_lexer_by_name, guess_lexer, \
|
||||
load_lexer_from_file, get_lexer_for_filename, find_lexer_class_for_filename
|
||||
from pip._vendor.pygments.lexers.special import TextLexer
|
||||
from pip._vendor.pygments.formatters.latex import LatexEmbeddedLexer, LatexFormatter
|
||||
from pip._vendor.pygments.formatters import get_all_formatters, get_formatter_by_name, \
|
||||
load_formatter_from_file, get_formatter_for_filename, find_formatter_class
|
||||
from pip._vendor.pygments.formatters.terminal import TerminalFormatter
|
||||
from pip._vendor.pygments.formatters.terminal256 import Terminal256Formatter, TerminalTrueColorFormatter
|
||||
from pip._vendor.pygments.filters import get_all_filters, find_filter_class
|
||||
from pip._vendor.pygments.styles import get_all_styles, get_style_by_name
|
||||
|
||||
|
||||
def _parse_options(o_strs):
|
||||
opts = {}
|
||||
if not o_strs:
|
||||
return opts
|
||||
for o_str in o_strs:
|
||||
if not o_str.strip():
|
||||
continue
|
||||
o_args = o_str.split(',')
|
||||
for o_arg in o_args:
|
||||
o_arg = o_arg.strip()
|
||||
try:
|
||||
o_key, o_val = o_arg.split('=', 1)
|
||||
o_key = o_key.strip()
|
||||
o_val = o_val.strip()
|
||||
except ValueError:
|
||||
opts[o_arg] = True
|
||||
else:
|
||||
opts[o_key] = o_val
|
||||
return opts
|
||||
|
||||
|
||||
def _parse_filters(f_strs):
|
||||
filters = []
|
||||
if not f_strs:
|
||||
return filters
|
||||
for f_str in f_strs:
|
||||
if ':' in f_str:
|
||||
fname, fopts = f_str.split(':', 1)
|
||||
filters.append((fname, _parse_options([fopts])))
|
||||
else:
|
||||
filters.append((f_str, {}))
|
||||
return filters
|
||||
|
||||
|
||||
def _print_help(what, name):
|
||||
try:
|
||||
if what == 'lexer':
|
||||
cls = get_lexer_by_name(name)
|
||||
print("Help on the %s lexer:" % cls.name)
|
||||
print(dedent(cls.__doc__))
|
||||
elif what == 'formatter':
|
||||
cls = find_formatter_class(name)
|
||||
print("Help on the %s formatter:" % cls.name)
|
||||
print(dedent(cls.__doc__))
|
||||
elif what == 'filter':
|
||||
cls = find_filter_class(name)
|
||||
print("Help on the %s filter:" % name)
|
||||
print(dedent(cls.__doc__))
|
||||
return 0
|
||||
except (AttributeError, ValueError):
|
||||
print("%s not found!" % what, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
def _print_list(what):
|
||||
if what == 'lexer':
|
||||
print()
|
||||
print("Lexers:")
|
||||
print("~~~~~~~")
|
||||
|
||||
info = []
|
||||
for fullname, names, exts, _ in get_all_lexers():
|
||||
tup = (', '.join(names)+':', fullname,
|
||||
exts and '(filenames ' + ', '.join(exts) + ')' or '')
|
||||
info.append(tup)
|
||||
info.sort()
|
||||
for i in info:
|
||||
print(('* %s\n %s %s') % i)
|
||||
|
||||
elif what == 'formatter':
|
||||
print()
|
||||
print("Formatters:")
|
||||
print("~~~~~~~~~~~")
|
||||
|
||||
info = []
|
||||
for cls in get_all_formatters():
|
||||
doc = docstring_headline(cls)
|
||||
tup = (', '.join(cls.aliases) + ':', doc, cls.filenames and
|
||||
'(filenames ' + ', '.join(cls.filenames) + ')' or '')
|
||||
info.append(tup)
|
||||
info.sort()
|
||||
for i in info:
|
||||
print(('* %s\n %s %s') % i)
|
||||
|
||||
elif what == 'filter':
|
||||
print()
|
||||
print("Filters:")
|
||||
print("~~~~~~~~")
|
||||
|
||||
for name in get_all_filters():
|
||||
cls = find_filter_class(name)
|
||||
print("* " + name + ':')
|
||||
print(" %s" % docstring_headline(cls))
|
||||
|
||||
elif what == 'style':
|
||||
print()
|
||||
print("Styles:")
|
||||
print("~~~~~~~")
|
||||
|
||||
for name in get_all_styles():
|
||||
cls = get_style_by_name(name)
|
||||
print("* " + name + ':')
|
||||
print(" %s" % docstring_headline(cls))
|
||||
|
||||
|
||||
def _print_list_as_json(requested_items):
|
||||
import json
|
||||
result = {}
|
||||
if 'lexer' in requested_items:
|
||||
info = {}
|
||||
for fullname, names, filenames, mimetypes in get_all_lexers():
|
||||
info[fullname] = {
|
||||
'aliases': names,
|
||||
'filenames': filenames,
|
||||
'mimetypes': mimetypes
|
||||
}
|
||||
result['lexers'] = info
|
||||
|
||||
if 'formatter' in requested_items:
|
||||
info = {}
|
||||
for cls in get_all_formatters():
|
||||
doc = docstring_headline(cls)
|
||||
info[cls.name] = {
|
||||
'aliases': cls.aliases,
|
||||
'filenames': cls.filenames,
|
||||
'doc': doc
|
||||
}
|
||||
result['formatters'] = info
|
||||
|
||||
if 'filter' in requested_items:
|
||||
info = {}
|
||||
for name in get_all_filters():
|
||||
cls = find_filter_class(name)
|
||||
info[name] = {
|
||||
'doc': docstring_headline(cls)
|
||||
}
|
||||
result['filters'] = info
|
||||
|
||||
if 'style' in requested_items:
|
||||
info = {}
|
||||
for name in get_all_styles():
|
||||
cls = get_style_by_name(name)
|
||||
info[name] = {
|
||||
'doc': docstring_headline(cls)
|
||||
}
|
||||
result['styles'] = info
|
||||
|
||||
json.dump(result, sys.stdout)
|
||||
|
||||
def main_inner(parser, argns):
|
||||
if argns.help:
|
||||
parser.print_help()
|
||||
return 0
|
||||
|
||||
if argns.V:
|
||||
print('Pygments version %s, (c) 2006-2023 by Georg Brandl, Matthäus '
|
||||
'Chajdas and contributors.' % __version__)
|
||||
return 0
|
||||
|
||||
def is_only_option(opt):
|
||||
return not any(v for (k, v) in vars(argns).items() if k != opt)
|
||||
|
||||
# handle ``pygmentize -L``
|
||||
if argns.L is not None:
|
||||
arg_set = set()
|
||||
for k, v in vars(argns).items():
|
||||
if v:
|
||||
arg_set.add(k)
|
||||
|
||||
arg_set.discard('L')
|
||||
arg_set.discard('json')
|
||||
|
||||
if arg_set:
|
||||
parser.print_help(sys.stderr)
|
||||
return 2
|
||||
|
||||
# print version
|
||||
if not argns.json:
|
||||
main(['', '-V'])
|
||||
allowed_types = {'lexer', 'formatter', 'filter', 'style'}
|
||||
largs = [arg.rstrip('s') for arg in argns.L]
|
||||
if any(arg not in allowed_types for arg in largs):
|
||||
parser.print_help(sys.stderr)
|
||||
return 0
|
||||
if not largs:
|
||||
largs = allowed_types
|
||||
if not argns.json:
|
||||
for arg in largs:
|
||||
_print_list(arg)
|
||||
else:
|
||||
_print_list_as_json(largs)
|
||||
return 0
|
||||
|
||||
# handle ``pygmentize -H``
|
||||
if argns.H:
|
||||
if not is_only_option('H'):
|
||||
parser.print_help(sys.stderr)
|
||||
return 2
|
||||
what, name = argns.H
|
||||
if what not in ('lexer', 'formatter', 'filter'):
|
||||
parser.print_help(sys.stderr)
|
||||
return 2
|
||||
return _print_help(what, name)
|
||||
|
||||
# parse -O options
|
||||
parsed_opts = _parse_options(argns.O or [])
|
||||
|
||||
# parse -P options
|
||||
for p_opt in argns.P or []:
|
||||
try:
|
||||
name, value = p_opt.split('=', 1)
|
||||
except ValueError:
|
||||
parsed_opts[p_opt] = True
|
||||
else:
|
||||
parsed_opts[name] = value
|
||||
|
||||
# encodings
|
||||
inencoding = parsed_opts.get('inencoding', parsed_opts.get('encoding'))
|
||||
outencoding = parsed_opts.get('outencoding', parsed_opts.get('encoding'))
|
||||
|
||||
# handle ``pygmentize -N``
|
||||
if argns.N:
|
||||
lexer = find_lexer_class_for_filename(argns.N)
|
||||
if lexer is None:
|
||||
lexer = TextLexer
|
||||
|
||||
print(lexer.aliases[0])
|
||||
return 0
|
||||
|
||||
# handle ``pygmentize -C``
|
||||
if argns.C:
|
||||
inp = sys.stdin.buffer.read()
|
||||
try:
|
||||
lexer = guess_lexer(inp, inencoding=inencoding)
|
||||
except ClassNotFound:
|
||||
lexer = TextLexer
|
||||
|
||||
print(lexer.aliases[0])
|
||||
return 0
|
||||
|
||||
# handle ``pygmentize -S``
|
||||
S_opt = argns.S
|
||||
a_opt = argns.a
|
||||
if S_opt is not None:
|
||||
f_opt = argns.f
|
||||
if not f_opt:
|
||||
parser.print_help(sys.stderr)
|
||||
return 2
|
||||
if argns.l or argns.INPUTFILE:
|
||||
parser.print_help(sys.stderr)
|
||||
return 2
|
||||
|
||||
try:
|
||||
parsed_opts['style'] = S_opt
|
||||
fmter = get_formatter_by_name(f_opt, **parsed_opts)
|
||||
except ClassNotFound as err:
|
||||
print(err, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(fmter.get_style_defs(a_opt or ''))
|
||||
return 0
|
||||
|
||||
# if no -S is given, -a is not allowed
|
||||
if argns.a is not None:
|
||||
parser.print_help(sys.stderr)
|
||||
return 2
|
||||
|
||||
# parse -F options
|
||||
F_opts = _parse_filters(argns.F or [])
|
||||
|
||||
# -x: allow custom (eXternal) lexers and formatters
|
||||
allow_custom_lexer_formatter = bool(argns.x)
|
||||
|
||||
# select lexer
|
||||
lexer = None
|
||||
|
||||
# given by name?
|
||||
lexername = argns.l
|
||||
if lexername:
|
||||
# custom lexer, located relative to user's cwd
|
||||
if allow_custom_lexer_formatter and '.py' in lexername:
|
||||
try:
|
||||
filename = None
|
||||
name = None
|
||||
if ':' in lexername:
|
||||
filename, name = lexername.rsplit(':', 1)
|
||||
|
||||
if '.py' in name:
|
||||
# This can happen on Windows: If the lexername is
|
||||
# C:\lexer.py -- return to normal load path in that case
|
||||
name = None
|
||||
|
||||
if filename and name:
|
||||
lexer = load_lexer_from_file(filename, name,
|
||||
**parsed_opts)
|
||||
else:
|
||||
lexer = load_lexer_from_file(lexername, **parsed_opts)
|
||||
except ClassNotFound as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
else:
|
||||
try:
|
||||
lexer = get_lexer_by_name(lexername, **parsed_opts)
|
||||
except (OptionError, ClassNotFound) as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# read input code
|
||||
code = None
|
||||
|
||||
if argns.INPUTFILE:
|
||||
if argns.s:
|
||||
print('Error: -s option not usable when input file specified',
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
|
||||
infn = argns.INPUTFILE
|
||||
try:
|
||||
with open(infn, 'rb') as infp:
|
||||
code = infp.read()
|
||||
except Exception as err:
|
||||
print('Error: cannot read infile:', err, file=sys.stderr)
|
||||
return 1
|
||||
if not inencoding:
|
||||
code, inencoding = guess_decode(code)
|
||||
|
||||
# do we have to guess the lexer?
|
||||
if not lexer:
|
||||
try:
|
||||
lexer = get_lexer_for_filename(infn, code, **parsed_opts)
|
||||
except ClassNotFound as err:
|
||||
if argns.g:
|
||||
try:
|
||||
lexer = guess_lexer(code, **parsed_opts)
|
||||
except ClassNotFound:
|
||||
lexer = TextLexer(**parsed_opts)
|
||||
else:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
except OptionError as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
elif not argns.s: # treat stdin as full file (-s support is later)
|
||||
# read code from terminal, always in binary mode since we want to
|
||||
# decode ourselves and be tolerant with it
|
||||
code = sys.stdin.buffer.read() # use .buffer to get a binary stream
|
||||
if not inencoding:
|
||||
code, inencoding = guess_decode_from_terminal(code, sys.stdin)
|
||||
# else the lexer will do the decoding
|
||||
if not lexer:
|
||||
try:
|
||||
lexer = guess_lexer(code, **parsed_opts)
|
||||
except ClassNotFound:
|
||||
lexer = TextLexer(**parsed_opts)
|
||||
|
||||
else: # -s option needs a lexer with -l
|
||||
if not lexer:
|
||||
print('Error: when using -s a lexer has to be selected with -l',
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
|
||||
# process filters
|
||||
for fname, fopts in F_opts:
|
||||
try:
|
||||
lexer.add_filter(fname, **fopts)
|
||||
except ClassNotFound as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# select formatter
|
||||
outfn = argns.o
|
||||
fmter = argns.f
|
||||
if fmter:
|
||||
# custom formatter, located relative to user's cwd
|
||||
if allow_custom_lexer_formatter and '.py' in fmter:
|
||||
try:
|
||||
filename = None
|
||||
name = None
|
||||
if ':' in fmter:
|
||||
# Same logic as above for custom lexer
|
||||
filename, name = fmter.rsplit(':', 1)
|
||||
|
||||
if '.py' in name:
|
||||
name = None
|
||||
|
||||
if filename and name:
|
||||
fmter = load_formatter_from_file(filename, name,
|
||||
**parsed_opts)
|
||||
else:
|
||||
fmter = load_formatter_from_file(fmter, **parsed_opts)
|
||||
except ClassNotFound as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
else:
|
||||
try:
|
||||
fmter = get_formatter_by_name(fmter, **parsed_opts)
|
||||
except (OptionError, ClassNotFound) as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if outfn:
|
||||
if not fmter:
|
||||
try:
|
||||
fmter = get_formatter_for_filename(outfn, **parsed_opts)
|
||||
except (OptionError, ClassNotFound) as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
try:
|
||||
outfile = open(outfn, 'wb')
|
||||
except Exception as err:
|
||||
print('Error: cannot open outfile:', err, file=sys.stderr)
|
||||
return 1
|
||||
else:
|
||||
if not fmter:
|
||||
if os.environ.get('COLORTERM','') in ('truecolor', '24bit'):
|
||||
fmter = TerminalTrueColorFormatter(**parsed_opts)
|
||||
elif '256' in os.environ.get('TERM', ''):
|
||||
fmter = Terminal256Formatter(**parsed_opts)
|
||||
else:
|
||||
fmter = TerminalFormatter(**parsed_opts)
|
||||
outfile = sys.stdout.buffer
|
||||
|
||||
# determine output encoding if not explicitly selected
|
||||
if not outencoding:
|
||||
if outfn:
|
||||
# output file? use lexer encoding for now (can still be None)
|
||||
fmter.encoding = inencoding
|
||||
else:
|
||||
# else use terminal encoding
|
||||
fmter.encoding = terminal_encoding(sys.stdout)
|
||||
|
||||
# provide coloring under Windows, if possible
|
||||
if not outfn and sys.platform in ('win32', 'cygwin') and \
|
||||
fmter.name in ('Terminal', 'Terminal256'): # pragma: no cover
|
||||
# unfortunately colorama doesn't support binary streams on Py3
|
||||
outfile = UnclosingTextIOWrapper(outfile, encoding=fmter.encoding)
|
||||
fmter.encoding = None
|
||||
try:
|
||||
import pip._vendor.colorama.initialise as colorama_initialise
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
outfile = colorama_initialise.wrap_stream(
|
||||
outfile, convert=None, strip=None, autoreset=False, wrap=True)
|
||||
|
||||
# When using the LaTeX formatter and the option `escapeinside` is
|
||||
# specified, we need a special lexer which collects escaped text
|
||||
# before running the chosen language lexer.
|
||||
escapeinside = parsed_opts.get('escapeinside', '')
|
||||
if len(escapeinside) == 2 and isinstance(fmter, LatexFormatter):
|
||||
left = escapeinside[0]
|
||||
right = escapeinside[1]
|
||||
lexer = LatexEmbeddedLexer(left, right, lexer)
|
||||
|
||||
# ... and do it!
|
||||
if not argns.s:
|
||||
# process whole input as per normal...
|
||||
try:
|
||||
highlight(code, lexer, fmter, outfile)
|
||||
finally:
|
||||
if outfn:
|
||||
outfile.close()
|
||||
return 0
|
||||
else:
|
||||
# line by line processing of stdin (eg: for 'tail -f')...
|
||||
try:
|
||||
while 1:
|
||||
line = sys.stdin.buffer.readline()
|
||||
if not line:
|
||||
break
|
||||
if not inencoding:
|
||||
line = guess_decode_from_terminal(line, sys.stdin)[0]
|
||||
highlight(line, lexer, fmter, outfile)
|
||||
if hasattr(outfile, 'flush'):
|
||||
outfile.flush()
|
||||
return 0
|
||||
except KeyboardInterrupt: # pragma: no cover
|
||||
return 0
|
||||
finally:
|
||||
if outfn:
|
||||
outfile.close()
|
||||
|
||||
|
||||
class HelpFormatter(argparse.HelpFormatter):
|
||||
def __init__(self, prog, indent_increment=2, max_help_position=16, width=None):
|
||||
if width is None:
|
||||
try:
|
||||
width = shutil.get_terminal_size().columns - 2
|
||||
except Exception:
|
||||
pass
|
||||
argparse.HelpFormatter.__init__(self, prog, indent_increment,
|
||||
max_help_position, width)
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
"""
|
||||
Main command line entry point.
|
||||
"""
|
||||
desc = "Highlight an input file and write the result to an output file."
|
||||
parser = argparse.ArgumentParser(description=desc, add_help=False,
|
||||
formatter_class=HelpFormatter)
|
||||
|
||||
operation = parser.add_argument_group('Main operation')
|
||||
lexersel = operation.add_mutually_exclusive_group()
|
||||
lexersel.add_argument(
|
||||
'-l', metavar='LEXER',
|
||||
help='Specify the lexer to use. (Query names with -L.) If not '
|
||||
'given and -g is not present, the lexer is guessed from the filename.')
|
||||
lexersel.add_argument(
|
||||
'-g', action='store_true',
|
||||
help='Guess the lexer from the file contents, or pass through '
|
||||
'as plain text if nothing can be guessed.')
|
||||
operation.add_argument(
|
||||
'-F', metavar='FILTER[:options]', action='append',
|
||||
help='Add a filter to the token stream. (Query names with -L.) '
|
||||
'Filter options are given after a colon if necessary.')
|
||||
operation.add_argument(
|
||||
'-f', metavar='FORMATTER',
|
||||
help='Specify the formatter to use. (Query names with -L.) '
|
||||
'If not given, the formatter is guessed from the output filename, '
|
||||
'and defaults to the terminal formatter if the output is to the '
|
||||
'terminal or an unknown file extension.')
|
||||
operation.add_argument(
|
||||
'-O', metavar='OPTION=value[,OPTION=value,...]', action='append',
|
||||
help='Give options to the lexer and formatter as a comma-separated '
|
||||
'list of key-value pairs. '
|
||||
'Example: `-O bg=light,python=cool`.')
|
||||
operation.add_argument(
|
||||
'-P', metavar='OPTION=value', action='append',
|
||||
help='Give a single option to the lexer and formatter - with this '
|
||||
'you can pass options whose value contains commas and equal signs. '
|
||||
'Example: `-P "heading=Pygments, the Python highlighter"`.')
|
||||
operation.add_argument(
|
||||
'-o', metavar='OUTPUTFILE',
|
||||
help='Where to write the output. Defaults to standard output.')
|
||||
|
||||
operation.add_argument(
|
||||
'INPUTFILE', nargs='?',
|
||||
help='Where to read the input. Defaults to standard input.')
|
||||
|
||||
flags = parser.add_argument_group('Operation flags')
|
||||
flags.add_argument(
|
||||
'-v', action='store_true',
|
||||
help='Print a detailed traceback on unhandled exceptions, which '
|
||||
'is useful for debugging and bug reports.')
|
||||
flags.add_argument(
|
||||
'-s', action='store_true',
|
||||
help='Process lines one at a time until EOF, rather than waiting to '
|
||||
'process the entire file. This only works for stdin, only for lexers '
|
||||
'with no line-spanning constructs, and is intended for streaming '
|
||||
'input such as you get from `tail -f`. '
|
||||
'Example usage: `tail -f sql.log | pygmentize -s -l sql`.')
|
||||
flags.add_argument(
|
||||
'-x', action='store_true',
|
||||
help='Allow custom lexers and formatters to be loaded from a .py file '
|
||||
'relative to the current working directory. For example, '
|
||||
'`-l ./customlexer.py -x`. By default, this option expects a file '
|
||||
'with a class named CustomLexer or CustomFormatter; you can also '
|
||||
'specify your own class name with a colon (`-l ./lexer.py:MyLexer`). '
|
||||
'Users should be very careful not to use this option with untrusted '
|
||||
'files, because it will import and run them.')
|
||||
flags.add_argument('--json', help='Output as JSON. This can '
|
||||
'be only used in conjunction with -L.',
|
||||
default=False,
|
||||
action='store_true')
|
||||
|
||||
special_modes_group = parser.add_argument_group(
|
||||
'Special modes - do not do any highlighting')
|
||||
special_modes = special_modes_group.add_mutually_exclusive_group()
|
||||
special_modes.add_argument(
|
||||
'-S', metavar='STYLE -f formatter',
|
||||
help='Print style definitions for STYLE for a formatter '
|
||||
'given with -f. The argument given by -a is formatter '
|
||||
'dependent.')
|
||||
special_modes.add_argument(
|
||||
'-L', nargs='*', metavar='WHAT',
|
||||
help='List lexers, formatters, styles or filters -- '
|
||||
'give additional arguments for the thing(s) you want to list '
|
||||
'(e.g. "styles"), or omit them to list everything.')
|
||||
special_modes.add_argument(
|
||||
'-N', metavar='FILENAME',
|
||||
help='Guess and print out a lexer name based solely on the given '
|
||||
'filename. Does not take input or highlight anything. If no specific '
|
||||
'lexer can be determined, "text" is printed.')
|
||||
special_modes.add_argument(
|
||||
'-C', action='store_true',
|
||||
help='Like -N, but print out a lexer name based solely on '
|
||||
'a given content from standard input.')
|
||||
special_modes.add_argument(
|
||||
'-H', action='store', nargs=2, metavar=('NAME', 'TYPE'),
|
||||
help='Print detailed help for the object <name> of type <type>, '
|
||||
'where <type> is one of "lexer", "formatter" or "filter".')
|
||||
special_modes.add_argument(
|
||||
'-V', action='store_true',
|
||||
help='Print the package version.')
|
||||
special_modes.add_argument(
|
||||
'-h', '--help', action='store_true',
|
||||
help='Print this help.')
|
||||
special_modes_group.add_argument(
|
||||
'-a', metavar='ARG',
|
||||
help='Formatter-specific additional argument for the -S (print '
|
||||
'style sheet) mode.')
|
||||
|
||||
argns = parser.parse_args(args[1:])
|
||||
|
||||
try:
|
||||
return main_inner(parser, argns)
|
||||
except BrokenPipeError:
|
||||
# someone closed our stdout, e.g. by quitting a pager.
|
||||
return 0
|
||||
except Exception:
|
||||
if argns.v:
|
||||
print(file=sys.stderr)
|
||||
print('*' * 65, file=sys.stderr)
|
||||
print('An unhandled exception occurred while highlighting.',
|
||||
file=sys.stderr)
|
||||
print('Please report the whole traceback to the issue tracker at',
|
||||
file=sys.stderr)
|
||||
print('<https://github.com/pygments/pygments/issues>.',
|
||||
file=sys.stderr)
|
||||
print('*' * 65, file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
raise
|
||||
import traceback
|
||||
info = traceback.format_exception(*sys.exc_info())
|
||||
msg = info[-1].strip()
|
||||
if len(info) >= 3:
|
||||
# extract relevant file and position info
|
||||
msg += '\n (f%s)' % info[-2].split('\n')[0].strip()[1:]
|
||||
print(file=sys.stderr)
|
||||
print('*** Error while highlighting:', file=sys.stderr)
|
||||
print(msg, file=sys.stderr)
|
||||
print('*** If this is a bug you want to report, please rerun with -v.',
|
||||
file=sys.stderr)
|
||||
return 1
|
||||
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
pygments.console
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Format colored console output.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
esc = "\x1b["
|
||||
|
||||
codes = {}
|
||||
codes[""] = ""
|
||||
codes["reset"] = esc + "39;49;00m"
|
||||
|
||||
codes["bold"] = esc + "01m"
|
||||
codes["faint"] = esc + "02m"
|
||||
codes["standout"] = esc + "03m"
|
||||
codes["underline"] = esc + "04m"
|
||||
codes["blink"] = esc + "05m"
|
||||
codes["overline"] = esc + "06m"
|
||||
|
||||
dark_colors = ["black", "red", "green", "yellow", "blue",
|
||||
"magenta", "cyan", "gray"]
|
||||
light_colors = ["brightblack", "brightred", "brightgreen", "brightyellow", "brightblue",
|
||||
"brightmagenta", "brightcyan", "white"]
|
||||
|
||||
x = 30
|
||||
for d, l in zip(dark_colors, light_colors):
|
||||
codes[d] = esc + "%im" % x
|
||||
codes[l] = esc + "%im" % (60 + x)
|
||||
x += 1
|
||||
|
||||
del d, l, x
|
||||
|
||||
codes["white"] = codes["bold"]
|
||||
|
||||
|
||||
def reset_color():
|
||||
return codes["reset"]
|
||||
|
||||
|
||||
def colorize(color_key, text):
|
||||
return codes[color_key] + text + codes["reset"]
|
||||
|
||||
|
||||
def ansiformat(attr, text):
|
||||
"""
|
||||
Format ``text`` with a color and/or some attributes::
|
||||
|
||||
color normal color
|
||||
*color* bold color
|
||||
_color_ underlined color
|
||||
+color+ blinking color
|
||||
"""
|
||||
result = []
|
||||
if attr[:1] == attr[-1:] == '+':
|
||||
result.append(codes['blink'])
|
||||
attr = attr[1:-1]
|
||||
if attr[:1] == attr[-1:] == '*':
|
||||
result.append(codes['bold'])
|
||||
attr = attr[1:-1]
|
||||
if attr[:1] == attr[-1:] == '_':
|
||||
result.append(codes['underline'])
|
||||
attr = attr[1:-1]
|
||||
result.append(codes[attr])
|
||||
result.append(text)
|
||||
result.append(codes['reset'])
|
||||
return ''.join(result)
|
||||
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
pygments.filter
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Module that implements the default filter.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
|
||||
def apply_filters(stream, filters, lexer=None):
|
||||
"""
|
||||
Use this method to apply an iterable of filters to
|
||||
a stream. If lexer is given it's forwarded to the
|
||||
filter, otherwise the filter receives `None`.
|
||||
"""
|
||||
def _apply(filter_, stream):
|
||||
yield from filter_.filter(lexer, stream)
|
||||
for filter_ in filters:
|
||||
stream = _apply(filter_, stream)
|
||||
return stream
|
||||
|
||||
|
||||
def simplefilter(f):
|
||||
"""
|
||||
Decorator that converts a function into a filter::
|
||||
|
||||
@simplefilter
|
||||
def lowercase(self, lexer, stream, options):
|
||||
for ttype, value in stream:
|
||||
yield ttype, value.lower()
|
||||
"""
|
||||
return type(f.__name__, (FunctionFilter,), {
|
||||
'__module__': getattr(f, '__module__'),
|
||||
'__doc__': f.__doc__,
|
||||
'function': f,
|
||||
})
|
||||
|
||||
|
||||
class Filter:
|
||||
"""
|
||||
Default filter. Subclass this class or use the `simplefilter`
|
||||
decorator to create own filters.
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
self.options = options
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class FunctionFilter(Filter):
|
||||
"""
|
||||
Abstract class used by `simplefilter` to create simple
|
||||
function filters on the fly. The `simplefilter` decorator
|
||||
automatically creates subclasses of this class for
|
||||
functions passed to it.
|
||||
"""
|
||||
function = None
|
||||
|
||||
def __init__(self, **options):
|
||||
if not hasattr(self, 'function'):
|
||||
raise TypeError('%r used without bound function' %
|
||||
self.__class__.__name__)
|
||||
Filter.__init__(self, **options)
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
# pylint: disable=not-callable
|
||||
yield from self.function(lexer, stream, self.options)
|
||||
@@ -0,0 +1,940 @@
|
||||
"""
|
||||
pygments.filters
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Module containing filter lookup functions and default
|
||||
filters.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from pip._vendor.pygments.token import String, Comment, Keyword, Name, Error, Whitespace, \
|
||||
string_to_tokentype
|
||||
from pip._vendor.pygments.filter import Filter
|
||||
from pip._vendor.pygments.util import get_list_opt, get_int_opt, get_bool_opt, \
|
||||
get_choice_opt, ClassNotFound, OptionError
|
||||
from pip._vendor.pygments.plugin import find_plugin_filters
|
||||
|
||||
|
||||
def find_filter_class(filtername):
|
||||
"""Lookup a filter by name. Return None if not found."""
|
||||
if filtername in FILTERS:
|
||||
return FILTERS[filtername]
|
||||
for name, cls in find_plugin_filters():
|
||||
if name == filtername:
|
||||
return cls
|
||||
return None
|
||||
|
||||
|
||||
def get_filter_by_name(filtername, **options):
|
||||
"""Return an instantiated filter.
|
||||
|
||||
Options are passed to the filter initializer if wanted.
|
||||
Raise a ClassNotFound if not found.
|
||||
"""
|
||||
cls = find_filter_class(filtername)
|
||||
if cls:
|
||||
return cls(**options)
|
||||
else:
|
||||
raise ClassNotFound('filter %r not found' % filtername)
|
||||
|
||||
|
||||
def get_all_filters():
|
||||
"""Return a generator of all filter names."""
|
||||
yield from FILTERS
|
||||
for name, _ in find_plugin_filters():
|
||||
yield name
|
||||
|
||||
|
||||
def _replace_special(ttype, value, regex, specialttype,
|
||||
replacefunc=lambda x: x):
|
||||
last = 0
|
||||
for match in regex.finditer(value):
|
||||
start, end = match.start(), match.end()
|
||||
if start != last:
|
||||
yield ttype, value[last:start]
|
||||
yield specialttype, replacefunc(value[start:end])
|
||||
last = end
|
||||
if last != len(value):
|
||||
yield ttype, value[last:]
|
||||
|
||||
|
||||
class CodeTagFilter(Filter):
|
||||
"""Highlight special code tags in comments and docstrings.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`codetags` : list of strings
|
||||
A list of strings that are flagged as code tags. The default is to
|
||||
highlight ``XXX``, ``TODO``, ``FIXME``, ``BUG`` and ``NOTE``.
|
||||
|
||||
.. versionchanged:: 2.13
|
||||
Now recognizes ``FIXME`` by default.
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
tags = get_list_opt(options, 'codetags',
|
||||
['XXX', 'TODO', 'FIXME', 'BUG', 'NOTE'])
|
||||
self.tag_re = re.compile(r'\b(%s)\b' % '|'.join([
|
||||
re.escape(tag) for tag in tags if tag
|
||||
]))
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
regex = self.tag_re
|
||||
for ttype, value in stream:
|
||||
if ttype in String.Doc or \
|
||||
ttype in Comment and \
|
||||
ttype not in Comment.Preproc:
|
||||
yield from _replace_special(ttype, value, regex, Comment.Special)
|
||||
else:
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class SymbolFilter(Filter):
|
||||
"""Convert mathematical symbols such as \\<longrightarrow> in Isabelle
|
||||
or \\longrightarrow in LaTeX into Unicode characters.
|
||||
|
||||
This is mostly useful for HTML or console output when you want to
|
||||
approximate the source rendering you'd see in an IDE.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`lang` : string
|
||||
The symbol language. Must be one of ``'isabelle'`` or
|
||||
``'latex'``. The default is ``'isabelle'``.
|
||||
"""
|
||||
|
||||
latex_symbols = {
|
||||
'\\alpha' : '\U000003b1',
|
||||
'\\beta' : '\U000003b2',
|
||||
'\\gamma' : '\U000003b3',
|
||||
'\\delta' : '\U000003b4',
|
||||
'\\varepsilon' : '\U000003b5',
|
||||
'\\zeta' : '\U000003b6',
|
||||
'\\eta' : '\U000003b7',
|
||||
'\\vartheta' : '\U000003b8',
|
||||
'\\iota' : '\U000003b9',
|
||||
'\\kappa' : '\U000003ba',
|
||||
'\\lambda' : '\U000003bb',
|
||||
'\\mu' : '\U000003bc',
|
||||
'\\nu' : '\U000003bd',
|
||||
'\\xi' : '\U000003be',
|
||||
'\\pi' : '\U000003c0',
|
||||
'\\varrho' : '\U000003c1',
|
||||
'\\sigma' : '\U000003c3',
|
||||
'\\tau' : '\U000003c4',
|
||||
'\\upsilon' : '\U000003c5',
|
||||
'\\varphi' : '\U000003c6',
|
||||
'\\chi' : '\U000003c7',
|
||||
'\\psi' : '\U000003c8',
|
||||
'\\omega' : '\U000003c9',
|
||||
'\\Gamma' : '\U00000393',
|
||||
'\\Delta' : '\U00000394',
|
||||
'\\Theta' : '\U00000398',
|
||||
'\\Lambda' : '\U0000039b',
|
||||
'\\Xi' : '\U0000039e',
|
||||
'\\Pi' : '\U000003a0',
|
||||
'\\Sigma' : '\U000003a3',
|
||||
'\\Upsilon' : '\U000003a5',
|
||||
'\\Phi' : '\U000003a6',
|
||||
'\\Psi' : '\U000003a8',
|
||||
'\\Omega' : '\U000003a9',
|
||||
'\\leftarrow' : '\U00002190',
|
||||
'\\longleftarrow' : '\U000027f5',
|
||||
'\\rightarrow' : '\U00002192',
|
||||
'\\longrightarrow' : '\U000027f6',
|
||||
'\\Leftarrow' : '\U000021d0',
|
||||
'\\Longleftarrow' : '\U000027f8',
|
||||
'\\Rightarrow' : '\U000021d2',
|
||||
'\\Longrightarrow' : '\U000027f9',
|
||||
'\\leftrightarrow' : '\U00002194',
|
||||
'\\longleftrightarrow' : '\U000027f7',
|
||||
'\\Leftrightarrow' : '\U000021d4',
|
||||
'\\Longleftrightarrow' : '\U000027fa',
|
||||
'\\mapsto' : '\U000021a6',
|
||||
'\\longmapsto' : '\U000027fc',
|
||||
'\\relbar' : '\U00002500',
|
||||
'\\Relbar' : '\U00002550',
|
||||
'\\hookleftarrow' : '\U000021a9',
|
||||
'\\hookrightarrow' : '\U000021aa',
|
||||
'\\leftharpoondown' : '\U000021bd',
|
||||
'\\rightharpoondown' : '\U000021c1',
|
||||
'\\leftharpoonup' : '\U000021bc',
|
||||
'\\rightharpoonup' : '\U000021c0',
|
||||
'\\rightleftharpoons' : '\U000021cc',
|
||||
'\\leadsto' : '\U0000219d',
|
||||
'\\downharpoonleft' : '\U000021c3',
|
||||
'\\downharpoonright' : '\U000021c2',
|
||||
'\\upharpoonleft' : '\U000021bf',
|
||||
'\\upharpoonright' : '\U000021be',
|
||||
'\\restriction' : '\U000021be',
|
||||
'\\uparrow' : '\U00002191',
|
||||
'\\Uparrow' : '\U000021d1',
|
||||
'\\downarrow' : '\U00002193',
|
||||
'\\Downarrow' : '\U000021d3',
|
||||
'\\updownarrow' : '\U00002195',
|
||||
'\\Updownarrow' : '\U000021d5',
|
||||
'\\langle' : '\U000027e8',
|
||||
'\\rangle' : '\U000027e9',
|
||||
'\\lceil' : '\U00002308',
|
||||
'\\rceil' : '\U00002309',
|
||||
'\\lfloor' : '\U0000230a',
|
||||
'\\rfloor' : '\U0000230b',
|
||||
'\\flqq' : '\U000000ab',
|
||||
'\\frqq' : '\U000000bb',
|
||||
'\\bot' : '\U000022a5',
|
||||
'\\top' : '\U000022a4',
|
||||
'\\wedge' : '\U00002227',
|
||||
'\\bigwedge' : '\U000022c0',
|
||||
'\\vee' : '\U00002228',
|
||||
'\\bigvee' : '\U000022c1',
|
||||
'\\forall' : '\U00002200',
|
||||
'\\exists' : '\U00002203',
|
||||
'\\nexists' : '\U00002204',
|
||||
'\\neg' : '\U000000ac',
|
||||
'\\Box' : '\U000025a1',
|
||||
'\\Diamond' : '\U000025c7',
|
||||
'\\vdash' : '\U000022a2',
|
||||
'\\models' : '\U000022a8',
|
||||
'\\dashv' : '\U000022a3',
|
||||
'\\surd' : '\U0000221a',
|
||||
'\\le' : '\U00002264',
|
||||
'\\ge' : '\U00002265',
|
||||
'\\ll' : '\U0000226a',
|
||||
'\\gg' : '\U0000226b',
|
||||
'\\lesssim' : '\U00002272',
|
||||
'\\gtrsim' : '\U00002273',
|
||||
'\\lessapprox' : '\U00002a85',
|
||||
'\\gtrapprox' : '\U00002a86',
|
||||
'\\in' : '\U00002208',
|
||||
'\\notin' : '\U00002209',
|
||||
'\\subset' : '\U00002282',
|
||||
'\\supset' : '\U00002283',
|
||||
'\\subseteq' : '\U00002286',
|
||||
'\\supseteq' : '\U00002287',
|
||||
'\\sqsubset' : '\U0000228f',
|
||||
'\\sqsupset' : '\U00002290',
|
||||
'\\sqsubseteq' : '\U00002291',
|
||||
'\\sqsupseteq' : '\U00002292',
|
||||
'\\cap' : '\U00002229',
|
||||
'\\bigcap' : '\U000022c2',
|
||||
'\\cup' : '\U0000222a',
|
||||
'\\bigcup' : '\U000022c3',
|
||||
'\\sqcup' : '\U00002294',
|
||||
'\\bigsqcup' : '\U00002a06',
|
||||
'\\sqcap' : '\U00002293',
|
||||
'\\Bigsqcap' : '\U00002a05',
|
||||
'\\setminus' : '\U00002216',
|
||||
'\\propto' : '\U0000221d',
|
||||
'\\uplus' : '\U0000228e',
|
||||
'\\bigplus' : '\U00002a04',
|
||||
'\\sim' : '\U0000223c',
|
||||
'\\doteq' : '\U00002250',
|
||||
'\\simeq' : '\U00002243',
|
||||
'\\approx' : '\U00002248',
|
||||
'\\asymp' : '\U0000224d',
|
||||
'\\cong' : '\U00002245',
|
||||
'\\equiv' : '\U00002261',
|
||||
'\\Join' : '\U000022c8',
|
||||
'\\bowtie' : '\U00002a1d',
|
||||
'\\prec' : '\U0000227a',
|
||||
'\\succ' : '\U0000227b',
|
||||
'\\preceq' : '\U0000227c',
|
||||
'\\succeq' : '\U0000227d',
|
||||
'\\parallel' : '\U00002225',
|
||||
'\\mid' : '\U000000a6',
|
||||
'\\pm' : '\U000000b1',
|
||||
'\\mp' : '\U00002213',
|
||||
'\\times' : '\U000000d7',
|
||||
'\\div' : '\U000000f7',
|
||||
'\\cdot' : '\U000022c5',
|
||||
'\\star' : '\U000022c6',
|
||||
'\\circ' : '\U00002218',
|
||||
'\\dagger' : '\U00002020',
|
||||
'\\ddagger' : '\U00002021',
|
||||
'\\lhd' : '\U000022b2',
|
||||
'\\rhd' : '\U000022b3',
|
||||
'\\unlhd' : '\U000022b4',
|
||||
'\\unrhd' : '\U000022b5',
|
||||
'\\triangleleft' : '\U000025c3',
|
||||
'\\triangleright' : '\U000025b9',
|
||||
'\\triangle' : '\U000025b3',
|
||||
'\\triangleq' : '\U0000225c',
|
||||
'\\oplus' : '\U00002295',
|
||||
'\\bigoplus' : '\U00002a01',
|
||||
'\\otimes' : '\U00002297',
|
||||
'\\bigotimes' : '\U00002a02',
|
||||
'\\odot' : '\U00002299',
|
||||
'\\bigodot' : '\U00002a00',
|
||||
'\\ominus' : '\U00002296',
|
||||
'\\oslash' : '\U00002298',
|
||||
'\\dots' : '\U00002026',
|
||||
'\\cdots' : '\U000022ef',
|
||||
'\\sum' : '\U00002211',
|
||||
'\\prod' : '\U0000220f',
|
||||
'\\coprod' : '\U00002210',
|
||||
'\\infty' : '\U0000221e',
|
||||
'\\int' : '\U0000222b',
|
||||
'\\oint' : '\U0000222e',
|
||||
'\\clubsuit' : '\U00002663',
|
||||
'\\diamondsuit' : '\U00002662',
|
||||
'\\heartsuit' : '\U00002661',
|
||||
'\\spadesuit' : '\U00002660',
|
||||
'\\aleph' : '\U00002135',
|
||||
'\\emptyset' : '\U00002205',
|
||||
'\\nabla' : '\U00002207',
|
||||
'\\partial' : '\U00002202',
|
||||
'\\flat' : '\U0000266d',
|
||||
'\\natural' : '\U0000266e',
|
||||
'\\sharp' : '\U0000266f',
|
||||
'\\angle' : '\U00002220',
|
||||
'\\copyright' : '\U000000a9',
|
||||
'\\textregistered' : '\U000000ae',
|
||||
'\\textonequarter' : '\U000000bc',
|
||||
'\\textonehalf' : '\U000000bd',
|
||||
'\\textthreequarters' : '\U000000be',
|
||||
'\\textordfeminine' : '\U000000aa',
|
||||
'\\textordmasculine' : '\U000000ba',
|
||||
'\\euro' : '\U000020ac',
|
||||
'\\pounds' : '\U000000a3',
|
||||
'\\yen' : '\U000000a5',
|
||||
'\\textcent' : '\U000000a2',
|
||||
'\\textcurrency' : '\U000000a4',
|
||||
'\\textdegree' : '\U000000b0',
|
||||
}
|
||||
|
||||
isabelle_symbols = {
|
||||
'\\<zero>' : '\U0001d7ec',
|
||||
'\\<one>' : '\U0001d7ed',
|
||||
'\\<two>' : '\U0001d7ee',
|
||||
'\\<three>' : '\U0001d7ef',
|
||||
'\\<four>' : '\U0001d7f0',
|
||||
'\\<five>' : '\U0001d7f1',
|
||||
'\\<six>' : '\U0001d7f2',
|
||||
'\\<seven>' : '\U0001d7f3',
|
||||
'\\<eight>' : '\U0001d7f4',
|
||||
'\\<nine>' : '\U0001d7f5',
|
||||
'\\<A>' : '\U0001d49c',
|
||||
'\\<B>' : '\U0000212c',
|
||||
'\\<C>' : '\U0001d49e',
|
||||
'\\<D>' : '\U0001d49f',
|
||||
'\\<E>' : '\U00002130',
|
||||
'\\<F>' : '\U00002131',
|
||||
'\\<G>' : '\U0001d4a2',
|
||||
'\\<H>' : '\U0000210b',
|
||||
'\\<I>' : '\U00002110',
|
||||
'\\<J>' : '\U0001d4a5',
|
||||
'\\<K>' : '\U0001d4a6',
|
||||
'\\<L>' : '\U00002112',
|
||||
'\\<M>' : '\U00002133',
|
||||
'\\<N>' : '\U0001d4a9',
|
||||
'\\<O>' : '\U0001d4aa',
|
||||
'\\<P>' : '\U0001d4ab',
|
||||
'\\<Q>' : '\U0001d4ac',
|
||||
'\\<R>' : '\U0000211b',
|
||||
'\\<S>' : '\U0001d4ae',
|
||||
'\\<T>' : '\U0001d4af',
|
||||
'\\<U>' : '\U0001d4b0',
|
||||
'\\<V>' : '\U0001d4b1',
|
||||
'\\<W>' : '\U0001d4b2',
|
||||
'\\<X>' : '\U0001d4b3',
|
||||
'\\<Y>' : '\U0001d4b4',
|
||||
'\\<Z>' : '\U0001d4b5',
|
||||
'\\<a>' : '\U0001d5ba',
|
||||
'\\<b>' : '\U0001d5bb',
|
||||
'\\<c>' : '\U0001d5bc',
|
||||
'\\<d>' : '\U0001d5bd',
|
||||
'\\<e>' : '\U0001d5be',
|
||||
'\\<f>' : '\U0001d5bf',
|
||||
'\\<g>' : '\U0001d5c0',
|
||||
'\\<h>' : '\U0001d5c1',
|
||||
'\\<i>' : '\U0001d5c2',
|
||||
'\\<j>' : '\U0001d5c3',
|
||||
'\\<k>' : '\U0001d5c4',
|
||||
'\\<l>' : '\U0001d5c5',
|
||||
'\\<m>' : '\U0001d5c6',
|
||||
'\\<n>' : '\U0001d5c7',
|
||||
'\\<o>' : '\U0001d5c8',
|
||||
'\\<p>' : '\U0001d5c9',
|
||||
'\\<q>' : '\U0001d5ca',
|
||||
'\\<r>' : '\U0001d5cb',
|
||||
'\\<s>' : '\U0001d5cc',
|
||||
'\\<t>' : '\U0001d5cd',
|
||||
'\\<u>' : '\U0001d5ce',
|
||||
'\\<v>' : '\U0001d5cf',
|
||||
'\\<w>' : '\U0001d5d0',
|
||||
'\\<x>' : '\U0001d5d1',
|
||||
'\\<y>' : '\U0001d5d2',
|
||||
'\\<z>' : '\U0001d5d3',
|
||||
'\\<AA>' : '\U0001d504',
|
||||
'\\<BB>' : '\U0001d505',
|
||||
'\\<CC>' : '\U0000212d',
|
||||
'\\<DD>' : '\U0001d507',
|
||||
'\\<EE>' : '\U0001d508',
|
||||
'\\<FF>' : '\U0001d509',
|
||||
'\\<GG>' : '\U0001d50a',
|
||||
'\\<HH>' : '\U0000210c',
|
||||
'\\<II>' : '\U00002111',
|
||||
'\\<JJ>' : '\U0001d50d',
|
||||
'\\<KK>' : '\U0001d50e',
|
||||
'\\<LL>' : '\U0001d50f',
|
||||
'\\<MM>' : '\U0001d510',
|
||||
'\\<NN>' : '\U0001d511',
|
||||
'\\<OO>' : '\U0001d512',
|
||||
'\\<PP>' : '\U0001d513',
|
||||
'\\<QQ>' : '\U0001d514',
|
||||
'\\<RR>' : '\U0000211c',
|
||||
'\\<SS>' : '\U0001d516',
|
||||
'\\<TT>' : '\U0001d517',
|
||||
'\\<UU>' : '\U0001d518',
|
||||
'\\<VV>' : '\U0001d519',
|
||||
'\\<WW>' : '\U0001d51a',
|
||||
'\\<XX>' : '\U0001d51b',
|
||||
'\\<YY>' : '\U0001d51c',
|
||||
'\\<ZZ>' : '\U00002128',
|
||||
'\\<aa>' : '\U0001d51e',
|
||||
'\\<bb>' : '\U0001d51f',
|
||||
'\\<cc>' : '\U0001d520',
|
||||
'\\<dd>' : '\U0001d521',
|
||||
'\\<ee>' : '\U0001d522',
|
||||
'\\<ff>' : '\U0001d523',
|
||||
'\\<gg>' : '\U0001d524',
|
||||
'\\<hh>' : '\U0001d525',
|
||||
'\\<ii>' : '\U0001d526',
|
||||
'\\<jj>' : '\U0001d527',
|
||||
'\\<kk>' : '\U0001d528',
|
||||
'\\<ll>' : '\U0001d529',
|
||||
'\\<mm>' : '\U0001d52a',
|
||||
'\\<nn>' : '\U0001d52b',
|
||||
'\\<oo>' : '\U0001d52c',
|
||||
'\\<pp>' : '\U0001d52d',
|
||||
'\\<qq>' : '\U0001d52e',
|
||||
'\\<rr>' : '\U0001d52f',
|
||||
'\\<ss>' : '\U0001d530',
|
||||
'\\<tt>' : '\U0001d531',
|
||||
'\\<uu>' : '\U0001d532',
|
||||
'\\<vv>' : '\U0001d533',
|
||||
'\\<ww>' : '\U0001d534',
|
||||
'\\<xx>' : '\U0001d535',
|
||||
'\\<yy>' : '\U0001d536',
|
||||
'\\<zz>' : '\U0001d537',
|
||||
'\\<alpha>' : '\U000003b1',
|
||||
'\\<beta>' : '\U000003b2',
|
||||
'\\<gamma>' : '\U000003b3',
|
||||
'\\<delta>' : '\U000003b4',
|
||||
'\\<epsilon>' : '\U000003b5',
|
||||
'\\<zeta>' : '\U000003b6',
|
||||
'\\<eta>' : '\U000003b7',
|
||||
'\\<theta>' : '\U000003b8',
|
||||
'\\<iota>' : '\U000003b9',
|
||||
'\\<kappa>' : '\U000003ba',
|
||||
'\\<lambda>' : '\U000003bb',
|
||||
'\\<mu>' : '\U000003bc',
|
||||
'\\<nu>' : '\U000003bd',
|
||||
'\\<xi>' : '\U000003be',
|
||||
'\\<pi>' : '\U000003c0',
|
||||
'\\<rho>' : '\U000003c1',
|
||||
'\\<sigma>' : '\U000003c3',
|
||||
'\\<tau>' : '\U000003c4',
|
||||
'\\<upsilon>' : '\U000003c5',
|
||||
'\\<phi>' : '\U000003c6',
|
||||
'\\<chi>' : '\U000003c7',
|
||||
'\\<psi>' : '\U000003c8',
|
||||
'\\<omega>' : '\U000003c9',
|
||||
'\\<Gamma>' : '\U00000393',
|
||||
'\\<Delta>' : '\U00000394',
|
||||
'\\<Theta>' : '\U00000398',
|
||||
'\\<Lambda>' : '\U0000039b',
|
||||
'\\<Xi>' : '\U0000039e',
|
||||
'\\<Pi>' : '\U000003a0',
|
||||
'\\<Sigma>' : '\U000003a3',
|
||||
'\\<Upsilon>' : '\U000003a5',
|
||||
'\\<Phi>' : '\U000003a6',
|
||||
'\\<Psi>' : '\U000003a8',
|
||||
'\\<Omega>' : '\U000003a9',
|
||||
'\\<bool>' : '\U0001d539',
|
||||
'\\<complex>' : '\U00002102',
|
||||
'\\<nat>' : '\U00002115',
|
||||
'\\<rat>' : '\U0000211a',
|
||||
'\\<real>' : '\U0000211d',
|
||||
'\\<int>' : '\U00002124',
|
||||
'\\<leftarrow>' : '\U00002190',
|
||||
'\\<longleftarrow>' : '\U000027f5',
|
||||
'\\<rightarrow>' : '\U00002192',
|
||||
'\\<longrightarrow>' : '\U000027f6',
|
||||
'\\<Leftarrow>' : '\U000021d0',
|
||||
'\\<Longleftarrow>' : '\U000027f8',
|
||||
'\\<Rightarrow>' : '\U000021d2',
|
||||
'\\<Longrightarrow>' : '\U000027f9',
|
||||
'\\<leftrightarrow>' : '\U00002194',
|
||||
'\\<longleftrightarrow>' : '\U000027f7',
|
||||
'\\<Leftrightarrow>' : '\U000021d4',
|
||||
'\\<Longleftrightarrow>' : '\U000027fa',
|
||||
'\\<mapsto>' : '\U000021a6',
|
||||
'\\<longmapsto>' : '\U000027fc',
|
||||
'\\<midarrow>' : '\U00002500',
|
||||
'\\<Midarrow>' : '\U00002550',
|
||||
'\\<hookleftarrow>' : '\U000021a9',
|
||||
'\\<hookrightarrow>' : '\U000021aa',
|
||||
'\\<leftharpoondown>' : '\U000021bd',
|
||||
'\\<rightharpoondown>' : '\U000021c1',
|
||||
'\\<leftharpoonup>' : '\U000021bc',
|
||||
'\\<rightharpoonup>' : '\U000021c0',
|
||||
'\\<rightleftharpoons>' : '\U000021cc',
|
||||
'\\<leadsto>' : '\U0000219d',
|
||||
'\\<downharpoonleft>' : '\U000021c3',
|
||||
'\\<downharpoonright>' : '\U000021c2',
|
||||
'\\<upharpoonleft>' : '\U000021bf',
|
||||
'\\<upharpoonright>' : '\U000021be',
|
||||
'\\<restriction>' : '\U000021be',
|
||||
'\\<Colon>' : '\U00002237',
|
||||
'\\<up>' : '\U00002191',
|
||||
'\\<Up>' : '\U000021d1',
|
||||
'\\<down>' : '\U00002193',
|
||||
'\\<Down>' : '\U000021d3',
|
||||
'\\<updown>' : '\U00002195',
|
||||
'\\<Updown>' : '\U000021d5',
|
||||
'\\<langle>' : '\U000027e8',
|
||||
'\\<rangle>' : '\U000027e9',
|
||||
'\\<lceil>' : '\U00002308',
|
||||
'\\<rceil>' : '\U00002309',
|
||||
'\\<lfloor>' : '\U0000230a',
|
||||
'\\<rfloor>' : '\U0000230b',
|
||||
'\\<lparr>' : '\U00002987',
|
||||
'\\<rparr>' : '\U00002988',
|
||||
'\\<lbrakk>' : '\U000027e6',
|
||||
'\\<rbrakk>' : '\U000027e7',
|
||||
'\\<lbrace>' : '\U00002983',
|
||||
'\\<rbrace>' : '\U00002984',
|
||||
'\\<guillemotleft>' : '\U000000ab',
|
||||
'\\<guillemotright>' : '\U000000bb',
|
||||
'\\<bottom>' : '\U000022a5',
|
||||
'\\<top>' : '\U000022a4',
|
||||
'\\<and>' : '\U00002227',
|
||||
'\\<And>' : '\U000022c0',
|
||||
'\\<or>' : '\U00002228',
|
||||
'\\<Or>' : '\U000022c1',
|
||||
'\\<forall>' : '\U00002200',
|
||||
'\\<exists>' : '\U00002203',
|
||||
'\\<nexists>' : '\U00002204',
|
||||
'\\<not>' : '\U000000ac',
|
||||
'\\<box>' : '\U000025a1',
|
||||
'\\<diamond>' : '\U000025c7',
|
||||
'\\<turnstile>' : '\U000022a2',
|
||||
'\\<Turnstile>' : '\U000022a8',
|
||||
'\\<tturnstile>' : '\U000022a9',
|
||||
'\\<TTurnstile>' : '\U000022ab',
|
||||
'\\<stileturn>' : '\U000022a3',
|
||||
'\\<surd>' : '\U0000221a',
|
||||
'\\<le>' : '\U00002264',
|
||||
'\\<ge>' : '\U00002265',
|
||||
'\\<lless>' : '\U0000226a',
|
||||
'\\<ggreater>' : '\U0000226b',
|
||||
'\\<lesssim>' : '\U00002272',
|
||||
'\\<greatersim>' : '\U00002273',
|
||||
'\\<lessapprox>' : '\U00002a85',
|
||||
'\\<greaterapprox>' : '\U00002a86',
|
||||
'\\<in>' : '\U00002208',
|
||||
'\\<notin>' : '\U00002209',
|
||||
'\\<subset>' : '\U00002282',
|
||||
'\\<supset>' : '\U00002283',
|
||||
'\\<subseteq>' : '\U00002286',
|
||||
'\\<supseteq>' : '\U00002287',
|
||||
'\\<sqsubset>' : '\U0000228f',
|
||||
'\\<sqsupset>' : '\U00002290',
|
||||
'\\<sqsubseteq>' : '\U00002291',
|
||||
'\\<sqsupseteq>' : '\U00002292',
|
||||
'\\<inter>' : '\U00002229',
|
||||
'\\<Inter>' : '\U000022c2',
|
||||
'\\<union>' : '\U0000222a',
|
||||
'\\<Union>' : '\U000022c3',
|
||||
'\\<squnion>' : '\U00002294',
|
||||
'\\<Squnion>' : '\U00002a06',
|
||||
'\\<sqinter>' : '\U00002293',
|
||||
'\\<Sqinter>' : '\U00002a05',
|
||||
'\\<setminus>' : '\U00002216',
|
||||
'\\<propto>' : '\U0000221d',
|
||||
'\\<uplus>' : '\U0000228e',
|
||||
'\\<Uplus>' : '\U00002a04',
|
||||
'\\<noteq>' : '\U00002260',
|
||||
'\\<sim>' : '\U0000223c',
|
||||
'\\<doteq>' : '\U00002250',
|
||||
'\\<simeq>' : '\U00002243',
|
||||
'\\<approx>' : '\U00002248',
|
||||
'\\<asymp>' : '\U0000224d',
|
||||
'\\<cong>' : '\U00002245',
|
||||
'\\<smile>' : '\U00002323',
|
||||
'\\<equiv>' : '\U00002261',
|
||||
'\\<frown>' : '\U00002322',
|
||||
'\\<Join>' : '\U000022c8',
|
||||
'\\<bowtie>' : '\U00002a1d',
|
||||
'\\<prec>' : '\U0000227a',
|
||||
'\\<succ>' : '\U0000227b',
|
||||
'\\<preceq>' : '\U0000227c',
|
||||
'\\<succeq>' : '\U0000227d',
|
||||
'\\<parallel>' : '\U00002225',
|
||||
'\\<bar>' : '\U000000a6',
|
||||
'\\<plusminus>' : '\U000000b1',
|
||||
'\\<minusplus>' : '\U00002213',
|
||||
'\\<times>' : '\U000000d7',
|
||||
'\\<div>' : '\U000000f7',
|
||||
'\\<cdot>' : '\U000022c5',
|
||||
'\\<star>' : '\U000022c6',
|
||||
'\\<bullet>' : '\U00002219',
|
||||
'\\<circ>' : '\U00002218',
|
||||
'\\<dagger>' : '\U00002020',
|
||||
'\\<ddagger>' : '\U00002021',
|
||||
'\\<lhd>' : '\U000022b2',
|
||||
'\\<rhd>' : '\U000022b3',
|
||||
'\\<unlhd>' : '\U000022b4',
|
||||
'\\<unrhd>' : '\U000022b5',
|
||||
'\\<triangleleft>' : '\U000025c3',
|
||||
'\\<triangleright>' : '\U000025b9',
|
||||
'\\<triangle>' : '\U000025b3',
|
||||
'\\<triangleq>' : '\U0000225c',
|
||||
'\\<oplus>' : '\U00002295',
|
||||
'\\<Oplus>' : '\U00002a01',
|
||||
'\\<otimes>' : '\U00002297',
|
||||
'\\<Otimes>' : '\U00002a02',
|
||||
'\\<odot>' : '\U00002299',
|
||||
'\\<Odot>' : '\U00002a00',
|
||||
'\\<ominus>' : '\U00002296',
|
||||
'\\<oslash>' : '\U00002298',
|
||||
'\\<dots>' : '\U00002026',
|
||||
'\\<cdots>' : '\U000022ef',
|
||||
'\\<Sum>' : '\U00002211',
|
||||
'\\<Prod>' : '\U0000220f',
|
||||
'\\<Coprod>' : '\U00002210',
|
||||
'\\<infinity>' : '\U0000221e',
|
||||
'\\<integral>' : '\U0000222b',
|
||||
'\\<ointegral>' : '\U0000222e',
|
||||
'\\<clubsuit>' : '\U00002663',
|
||||
'\\<diamondsuit>' : '\U00002662',
|
||||
'\\<heartsuit>' : '\U00002661',
|
||||
'\\<spadesuit>' : '\U00002660',
|
||||
'\\<aleph>' : '\U00002135',
|
||||
'\\<emptyset>' : '\U00002205',
|
||||
'\\<nabla>' : '\U00002207',
|
||||
'\\<partial>' : '\U00002202',
|
||||
'\\<flat>' : '\U0000266d',
|
||||
'\\<natural>' : '\U0000266e',
|
||||
'\\<sharp>' : '\U0000266f',
|
||||
'\\<angle>' : '\U00002220',
|
||||
'\\<copyright>' : '\U000000a9',
|
||||
'\\<registered>' : '\U000000ae',
|
||||
'\\<hyphen>' : '\U000000ad',
|
||||
'\\<inverse>' : '\U000000af',
|
||||
'\\<onequarter>' : '\U000000bc',
|
||||
'\\<onehalf>' : '\U000000bd',
|
||||
'\\<threequarters>' : '\U000000be',
|
||||
'\\<ordfeminine>' : '\U000000aa',
|
||||
'\\<ordmasculine>' : '\U000000ba',
|
||||
'\\<section>' : '\U000000a7',
|
||||
'\\<paragraph>' : '\U000000b6',
|
||||
'\\<exclamdown>' : '\U000000a1',
|
||||
'\\<questiondown>' : '\U000000bf',
|
||||
'\\<euro>' : '\U000020ac',
|
||||
'\\<pounds>' : '\U000000a3',
|
||||
'\\<yen>' : '\U000000a5',
|
||||
'\\<cent>' : '\U000000a2',
|
||||
'\\<currency>' : '\U000000a4',
|
||||
'\\<degree>' : '\U000000b0',
|
||||
'\\<amalg>' : '\U00002a3f',
|
||||
'\\<mho>' : '\U00002127',
|
||||
'\\<lozenge>' : '\U000025ca',
|
||||
'\\<wp>' : '\U00002118',
|
||||
'\\<wrong>' : '\U00002240',
|
||||
'\\<struct>' : '\U000022c4',
|
||||
'\\<acute>' : '\U000000b4',
|
||||
'\\<index>' : '\U00000131',
|
||||
'\\<dieresis>' : '\U000000a8',
|
||||
'\\<cedilla>' : '\U000000b8',
|
||||
'\\<hungarumlaut>' : '\U000002dd',
|
||||
'\\<some>' : '\U000003f5',
|
||||
'\\<newline>' : '\U000023ce',
|
||||
'\\<open>' : '\U00002039',
|
||||
'\\<close>' : '\U0000203a',
|
||||
'\\<here>' : '\U00002302',
|
||||
'\\<^sub>' : '\U000021e9',
|
||||
'\\<^sup>' : '\U000021e7',
|
||||
'\\<^bold>' : '\U00002759',
|
||||
'\\<^bsub>' : '\U000021d8',
|
||||
'\\<^esub>' : '\U000021d9',
|
||||
'\\<^bsup>' : '\U000021d7',
|
||||
'\\<^esup>' : '\U000021d6',
|
||||
}
|
||||
|
||||
lang_map = {'isabelle' : isabelle_symbols, 'latex' : latex_symbols}
|
||||
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
lang = get_choice_opt(options, 'lang',
|
||||
['isabelle', 'latex'], 'isabelle')
|
||||
self.symbols = self.lang_map[lang]
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
for ttype, value in stream:
|
||||
if value in self.symbols:
|
||||
yield ttype, self.symbols[value]
|
||||
else:
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class KeywordCaseFilter(Filter):
|
||||
"""Convert keywords to lowercase or uppercase or capitalize them, which
|
||||
means first letter uppercase, rest lowercase.
|
||||
|
||||
This can be useful e.g. if you highlight Pascal code and want to adapt the
|
||||
code to your styleguide.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`case` : string
|
||||
The casing to convert keywords to. Must be one of ``'lower'``,
|
||||
``'upper'`` or ``'capitalize'``. The default is ``'lower'``.
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
case = get_choice_opt(options, 'case',
|
||||
['lower', 'upper', 'capitalize'], 'lower')
|
||||
self.convert = getattr(str, case)
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
for ttype, value in stream:
|
||||
if ttype in Keyword:
|
||||
yield ttype, self.convert(value)
|
||||
else:
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class NameHighlightFilter(Filter):
|
||||
"""Highlight a normal Name (and Name.*) token with a different token type.
|
||||
|
||||
Example::
|
||||
|
||||
filter = NameHighlightFilter(
|
||||
names=['foo', 'bar', 'baz'],
|
||||
tokentype=Name.Function,
|
||||
)
|
||||
|
||||
This would highlight the names "foo", "bar" and "baz"
|
||||
as functions. `Name.Function` is the default token type.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`names` : list of strings
|
||||
A list of names that should be given the different token type.
|
||||
There is no default.
|
||||
`tokentype` : TokenType or string
|
||||
A token type or a string containing a token type name that is
|
||||
used for highlighting the strings in `names`. The default is
|
||||
`Name.Function`.
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
self.names = set(get_list_opt(options, 'names', []))
|
||||
tokentype = options.get('tokentype')
|
||||
if tokentype:
|
||||
self.tokentype = string_to_tokentype(tokentype)
|
||||
else:
|
||||
self.tokentype = Name.Function
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
for ttype, value in stream:
|
||||
if ttype in Name and value in self.names:
|
||||
yield self.tokentype, value
|
||||
else:
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class ErrorToken(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RaiseOnErrorTokenFilter(Filter):
|
||||
"""Raise an exception when the lexer generates an error token.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`excclass` : Exception class
|
||||
The exception class to raise.
|
||||
The default is `pygments.filters.ErrorToken`.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
self.exception = options.get('excclass', ErrorToken)
|
||||
try:
|
||||
# issubclass() will raise TypeError if first argument is not a class
|
||||
if not issubclass(self.exception, Exception):
|
||||
raise TypeError
|
||||
except TypeError:
|
||||
raise OptionError('excclass option is not an exception class')
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
for ttype, value in stream:
|
||||
if ttype is Error:
|
||||
raise self.exception(value)
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class VisibleWhitespaceFilter(Filter):
|
||||
"""Convert tabs, newlines and/or spaces to visible characters.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`spaces` : string or bool
|
||||
If this is a one-character string, spaces will be replaces by this string.
|
||||
If it is another true value, spaces will be replaced by ``·`` (unicode
|
||||
MIDDLE DOT). If it is a false value, spaces will not be replaced. The
|
||||
default is ``False``.
|
||||
`tabs` : string or bool
|
||||
The same as for `spaces`, but the default replacement character is ``»``
|
||||
(unicode RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK). The default value
|
||||
is ``False``. Note: this will not work if the `tabsize` option for the
|
||||
lexer is nonzero, as tabs will already have been expanded then.
|
||||
`tabsize` : int
|
||||
If tabs are to be replaced by this filter (see the `tabs` option), this
|
||||
is the total number of characters that a tab should be expanded to.
|
||||
The default is ``8``.
|
||||
`newlines` : string or bool
|
||||
The same as for `spaces`, but the default replacement character is ``¶``
|
||||
(unicode PILCROW SIGN). The default value is ``False``.
|
||||
`wstokentype` : bool
|
||||
If true, give whitespace the special `Whitespace` token type. This allows
|
||||
styling the visible whitespace differently (e.g. greyed out), but it can
|
||||
disrupt background colors. The default is ``True``.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
for name, default in [('spaces', '·'),
|
||||
('tabs', '»'),
|
||||
('newlines', '¶')]:
|
||||
opt = options.get(name, False)
|
||||
if isinstance(opt, str) and len(opt) == 1:
|
||||
setattr(self, name, opt)
|
||||
else:
|
||||
setattr(self, name, (opt and default or ''))
|
||||
tabsize = get_int_opt(options, 'tabsize', 8)
|
||||
if self.tabs:
|
||||
self.tabs += ' ' * (tabsize - 1)
|
||||
if self.newlines:
|
||||
self.newlines += '\n'
|
||||
self.wstt = get_bool_opt(options, 'wstokentype', True)
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
if self.wstt:
|
||||
spaces = self.spaces or ' '
|
||||
tabs = self.tabs or '\t'
|
||||
newlines = self.newlines or '\n'
|
||||
regex = re.compile(r'\s')
|
||||
|
||||
def replacefunc(wschar):
|
||||
if wschar == ' ':
|
||||
return spaces
|
||||
elif wschar == '\t':
|
||||
return tabs
|
||||
elif wschar == '\n':
|
||||
return newlines
|
||||
return wschar
|
||||
|
||||
for ttype, value in stream:
|
||||
yield from _replace_special(ttype, value, regex, Whitespace,
|
||||
replacefunc)
|
||||
else:
|
||||
spaces, tabs, newlines = self.spaces, self.tabs, self.newlines
|
||||
# simpler processing
|
||||
for ttype, value in stream:
|
||||
if spaces:
|
||||
value = value.replace(' ', spaces)
|
||||
if tabs:
|
||||
value = value.replace('\t', tabs)
|
||||
if newlines:
|
||||
value = value.replace('\n', newlines)
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class GobbleFilter(Filter):
|
||||
"""Gobbles source code lines (eats initial characters).
|
||||
|
||||
This filter drops the first ``n`` characters off every line of code. This
|
||||
may be useful when the source code fed to the lexer is indented by a fixed
|
||||
amount of space that isn't desired in the output.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`n` : int
|
||||
The number of characters to gobble.
|
||||
|
||||
.. versionadded:: 1.2
|
||||
"""
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
self.n = get_int_opt(options, 'n', 0)
|
||||
|
||||
def gobble(self, value, left):
|
||||
if left < len(value):
|
||||
return value[left:], 0
|
||||
else:
|
||||
return '', left - len(value)
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
n = self.n
|
||||
left = n # How many characters left to gobble.
|
||||
for ttype, value in stream:
|
||||
# Remove ``left`` tokens from first line, ``n`` from all others.
|
||||
parts = value.split('\n')
|
||||
(parts[0], left) = self.gobble(parts[0], left)
|
||||
for i in range(1, len(parts)):
|
||||
(parts[i], left) = self.gobble(parts[i], n)
|
||||
value = '\n'.join(parts)
|
||||
|
||||
if value != '':
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class TokenMergeFilter(Filter):
|
||||
"""Merges consecutive tokens with the same token type in the output
|
||||
stream of a lexer.
|
||||
|
||||
.. versionadded:: 1.2
|
||||
"""
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
current_type = None
|
||||
current_value = None
|
||||
for ttype, value in stream:
|
||||
if ttype is current_type:
|
||||
current_value += value
|
||||
else:
|
||||
if current_type is not None:
|
||||
yield current_type, current_value
|
||||
current_type = ttype
|
||||
current_value = value
|
||||
if current_type is not None:
|
||||
yield current_type, current_value
|
||||
|
||||
|
||||
FILTERS = {
|
||||
'codetagify': CodeTagFilter,
|
||||
'keywordcase': KeywordCaseFilter,
|
||||
'highlight': NameHighlightFilter,
|
||||
'raiseonerror': RaiseOnErrorTokenFilter,
|
||||
'whitespace': VisibleWhitespaceFilter,
|
||||
'gobble': GobbleFilter,
|
||||
'tokenmerge': TokenMergeFilter,
|
||||
'symbols': SymbolFilter,
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,124 @@
|
||||
"""
|
||||
pygments.formatter
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Base formatter class.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import codecs
|
||||
|
||||
from pip._vendor.pygments.util import get_bool_opt
|
||||
from pip._vendor.pygments.styles import get_style_by_name
|
||||
|
||||
__all__ = ['Formatter']
|
||||
|
||||
|
||||
def _lookup_style(style):
|
||||
if isinstance(style, str):
|
||||
return get_style_by_name(style)
|
||||
return style
|
||||
|
||||
|
||||
class Formatter:
|
||||
"""
|
||||
Converts a token stream to text.
|
||||
|
||||
Formatters should have attributes to help selecting them. These
|
||||
are similar to the corresponding :class:`~pygments.lexer.Lexer`
|
||||
attributes.
|
||||
|
||||
.. autoattribute:: name
|
||||
:no-value:
|
||||
|
||||
.. autoattribute:: aliases
|
||||
:no-value:
|
||||
|
||||
.. autoattribute:: filenames
|
||||
:no-value:
|
||||
|
||||
You can pass options as keyword arguments to the constructor.
|
||||
All formatters accept these basic options:
|
||||
|
||||
``style``
|
||||
The style to use, can be a string or a Style subclass
|
||||
(default: "default"). Not used by e.g. the
|
||||
TerminalFormatter.
|
||||
``full``
|
||||
Tells the formatter to output a "full" document, i.e.
|
||||
a complete self-contained document. This doesn't have
|
||||
any effect for some formatters (default: false).
|
||||
``title``
|
||||
If ``full`` is true, the title that should be used to
|
||||
caption the document (default: '').
|
||||
``encoding``
|
||||
If given, must be an encoding name. This will be used to
|
||||
convert the Unicode token strings to byte strings in the
|
||||
output. If it is "" or None, Unicode strings will be written
|
||||
to the output file, which most file-like objects do not
|
||||
support (default: None).
|
||||
``outencoding``
|
||||
Overrides ``encoding`` if given.
|
||||
|
||||
"""
|
||||
|
||||
#: Full name for the formatter, in human-readable form.
|
||||
name = None
|
||||
|
||||
#: A list of short, unique identifiers that can be used to lookup
|
||||
#: the formatter from a list, e.g. using :func:`.get_formatter_by_name()`.
|
||||
aliases = []
|
||||
|
||||
#: A list of fnmatch patterns that match filenames for which this
|
||||
#: formatter can produce output. The patterns in this list should be unique
|
||||
#: among all formatters.
|
||||
filenames = []
|
||||
|
||||
#: If True, this formatter outputs Unicode strings when no encoding
|
||||
#: option is given.
|
||||
unicodeoutput = True
|
||||
|
||||
def __init__(self, **options):
|
||||
"""
|
||||
As with lexers, this constructor takes arbitrary optional arguments,
|
||||
and if you override it, you should first process your own options, then
|
||||
call the base class implementation.
|
||||
"""
|
||||
self.style = _lookup_style(options.get('style', 'default'))
|
||||
self.full = get_bool_opt(options, 'full', False)
|
||||
self.title = options.get('title', '')
|
||||
self.encoding = options.get('encoding', None) or None
|
||||
if self.encoding in ('guess', 'chardet'):
|
||||
# can happen for e.g. pygmentize -O encoding=guess
|
||||
self.encoding = 'utf-8'
|
||||
self.encoding = options.get('outencoding') or self.encoding
|
||||
self.options = options
|
||||
|
||||
def get_style_defs(self, arg=''):
|
||||
"""
|
||||
This method must return statements or declarations suitable to define
|
||||
the current style for subsequent highlighted text (e.g. CSS classes
|
||||
in the `HTMLFormatter`).
|
||||
|
||||
The optional argument `arg` can be used to modify the generation and
|
||||
is formatter dependent (it is standardized because it can be given on
|
||||
the command line).
|
||||
|
||||
This method is called by the ``-S`` :doc:`command-line option <cmdline>`,
|
||||
the `arg` is then given by the ``-a`` option.
|
||||
"""
|
||||
return ''
|
||||
|
||||
def format(self, tokensource, outfile):
|
||||
"""
|
||||
This method must format the tokens from the `tokensource` iterable and
|
||||
write the formatted version to the file object `outfile`.
|
||||
|
||||
Formatter options can control how exactly the tokens are converted.
|
||||
"""
|
||||
if self.encoding:
|
||||
# wrap the outfile in a StreamWriter
|
||||
outfile = codecs.lookup(self.encoding)[3](outfile)
|
||||
return self.format_unencoded(tokensource, outfile)
|
||||
@@ -0,0 +1,158 @@
|
||||
"""
|
||||
pygments.formatters
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Pygments formatters.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
import fnmatch
|
||||
from os.path import basename
|
||||
|
||||
from pip._vendor.pygments.formatters._mapping import FORMATTERS
|
||||
from pip._vendor.pygments.plugin import find_plugin_formatters
|
||||
from pip._vendor.pygments.util import ClassNotFound
|
||||
|
||||
__all__ = ['get_formatter_by_name', 'get_formatter_for_filename',
|
||||
'get_all_formatters', 'load_formatter_from_file'] + list(FORMATTERS)
|
||||
|
||||
_formatter_cache = {} # classes by name
|
||||
_pattern_cache = {}
|
||||
|
||||
|
||||
def _fn_matches(fn, glob):
|
||||
"""Return whether the supplied file name fn matches pattern filename."""
|
||||
if glob not in _pattern_cache:
|
||||
pattern = _pattern_cache[glob] = re.compile(fnmatch.translate(glob))
|
||||
return pattern.match(fn)
|
||||
return _pattern_cache[glob].match(fn)
|
||||
|
||||
|
||||
def _load_formatters(module_name):
|
||||
"""Load a formatter (and all others in the module too)."""
|
||||
mod = __import__(module_name, None, None, ['__all__'])
|
||||
for formatter_name in mod.__all__:
|
||||
cls = getattr(mod, formatter_name)
|
||||
_formatter_cache[cls.name] = cls
|
||||
|
||||
|
||||
def get_all_formatters():
|
||||
"""Return a generator for all formatter classes."""
|
||||
# NB: this returns formatter classes, not info like get_all_lexers().
|
||||
for info in FORMATTERS.values():
|
||||
if info[1] not in _formatter_cache:
|
||||
_load_formatters(info[0])
|
||||
yield _formatter_cache[info[1]]
|
||||
for _, formatter in find_plugin_formatters():
|
||||
yield formatter
|
||||
|
||||
|
||||
def find_formatter_class(alias):
|
||||
"""Lookup a formatter by alias.
|
||||
|
||||
Returns None if not found.
|
||||
"""
|
||||
for module_name, name, aliases, _, _ in FORMATTERS.values():
|
||||
if alias in aliases:
|
||||
if name not in _formatter_cache:
|
||||
_load_formatters(module_name)
|
||||
return _formatter_cache[name]
|
||||
for _, cls in find_plugin_formatters():
|
||||
if alias in cls.aliases:
|
||||
return cls
|
||||
|
||||
|
||||
def get_formatter_by_name(_alias, **options):
|
||||
"""
|
||||
Return an instance of a :class:`.Formatter` subclass that has `alias` in its
|
||||
aliases list. The formatter is given the `options` at its instantiation.
|
||||
|
||||
Will raise :exc:`pygments.util.ClassNotFound` if no formatter with that
|
||||
alias is found.
|
||||
"""
|
||||
cls = find_formatter_class(_alias)
|
||||
if cls is None:
|
||||
raise ClassNotFound("no formatter found for name %r" % _alias)
|
||||
return cls(**options)
|
||||
|
||||
|
||||
def load_formatter_from_file(filename, formattername="CustomFormatter", **options):
|
||||
"""
|
||||
Return a `Formatter` subclass instance loaded from the provided file, relative
|
||||
to the current directory.
|
||||
|
||||
The file is expected to contain a Formatter class named ``formattername``
|
||||
(by default, CustomFormatter). Users should be very careful with the input, because
|
||||
this method is equivalent to running ``eval()`` on the input file. The formatter is
|
||||
given the `options` at its instantiation.
|
||||
|
||||
:exc:`pygments.util.ClassNotFound` is raised if there are any errors loading
|
||||
the formatter.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
"""
|
||||
try:
|
||||
# This empty dict will contain the namespace for the exec'd file
|
||||
custom_namespace = {}
|
||||
with open(filename, 'rb') as f:
|
||||
exec(f.read(), custom_namespace)
|
||||
# Retrieve the class `formattername` from that namespace
|
||||
if formattername not in custom_namespace:
|
||||
raise ClassNotFound('no valid %s class found in %s' %
|
||||
(formattername, filename))
|
||||
formatter_class = custom_namespace[formattername]
|
||||
# And finally instantiate it with the options
|
||||
return formatter_class(**options)
|
||||
except OSError as err:
|
||||
raise ClassNotFound('cannot read %s: %s' % (filename, err))
|
||||
except ClassNotFound:
|
||||
raise
|
||||
except Exception as err:
|
||||
raise ClassNotFound('error when loading custom formatter: %s' % err)
|
||||
|
||||
|
||||
def get_formatter_for_filename(fn, **options):
|
||||
"""
|
||||
Return a :class:`.Formatter` subclass instance that has a filename pattern
|
||||
matching `fn`. The formatter is given the `options` at its instantiation.
|
||||
|
||||
Will raise :exc:`pygments.util.ClassNotFound` if no formatter for that filename
|
||||
is found.
|
||||
"""
|
||||
fn = basename(fn)
|
||||
for modname, name, _, filenames, _ in FORMATTERS.values():
|
||||
for filename in filenames:
|
||||
if _fn_matches(fn, filename):
|
||||
if name not in _formatter_cache:
|
||||
_load_formatters(modname)
|
||||
return _formatter_cache[name](**options)
|
||||
for cls in find_plugin_formatters():
|
||||
for filename in cls.filenames:
|
||||
if _fn_matches(fn, filename):
|
||||
return cls(**options)
|
||||
raise ClassNotFound("no formatter found for file name %r" % fn)
|
||||
|
||||
|
||||
class _automodule(types.ModuleType):
|
||||
"""Automatically import formatters."""
|
||||
|
||||
def __getattr__(self, name):
|
||||
info = FORMATTERS.get(name)
|
||||
if info:
|
||||
_load_formatters(info[0])
|
||||
cls = _formatter_cache[info[1]]
|
||||
setattr(self, name, cls)
|
||||
return cls
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
oldmod = sys.modules[__name__]
|
||||
newmod = _automodule(__name__)
|
||||
newmod.__dict__.update(oldmod.__dict__)
|
||||
sys.modules[__name__] = newmod
|
||||
del newmod.newmod, newmod.oldmod, newmod.sys, newmod.types
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,23 @@
|
||||
# Automatically generated by scripts/gen_mapfiles.py.
|
||||
# DO NOT EDIT BY HAND; run `tox -e mapfiles` instead.
|
||||
|
||||
FORMATTERS = {
|
||||
'BBCodeFormatter': ('pygments.formatters.bbcode', 'BBCode', ('bbcode', 'bb'), (), 'Format tokens with BBcodes. These formatting codes are used by many bulletin boards, so you can highlight your sourcecode with pygments before posting it there.'),
|
||||
'BmpImageFormatter': ('pygments.formatters.img', 'img_bmp', ('bmp', 'bitmap'), ('*.bmp',), 'Create a bitmap image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
|
||||
'GifImageFormatter': ('pygments.formatters.img', 'img_gif', ('gif',), ('*.gif',), 'Create a GIF image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
|
||||
'GroffFormatter': ('pygments.formatters.groff', 'groff', ('groff', 'troff', 'roff'), (), 'Format tokens with groff escapes to change their color and font style.'),
|
||||
'HtmlFormatter': ('pygments.formatters.html', 'HTML', ('html',), ('*.html', '*.htm'), "Format tokens as HTML 4 ``<span>`` tags. By default, the content is enclosed in a ``<pre>`` tag, itself wrapped in a ``<div>`` tag (but see the `nowrap` option). The ``<div>``'s CSS class can be set by the `cssclass` option."),
|
||||
'IRCFormatter': ('pygments.formatters.irc', 'IRC', ('irc', 'IRC'), (), 'Format tokens with IRC color sequences'),
|
||||
'ImageFormatter': ('pygments.formatters.img', 'img', ('img', 'IMG', 'png'), ('*.png',), 'Create a PNG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
|
||||
'JpgImageFormatter': ('pygments.formatters.img', 'img_jpg', ('jpg', 'jpeg'), ('*.jpg',), 'Create a JPEG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
|
||||
'LatexFormatter': ('pygments.formatters.latex', 'LaTeX', ('latex', 'tex'), ('*.tex',), 'Format tokens as LaTeX code. This needs the `fancyvrb` and `color` standard packages.'),
|
||||
'NullFormatter': ('pygments.formatters.other', 'Text only', ('text', 'null'), ('*.txt',), 'Output the text unchanged without any formatting.'),
|
||||
'PangoMarkupFormatter': ('pygments.formatters.pangomarkup', 'Pango Markup', ('pango', 'pangomarkup'), (), 'Format tokens as Pango Markup code. It can then be rendered to an SVG.'),
|
||||
'RawTokenFormatter': ('pygments.formatters.other', 'Raw tokens', ('raw', 'tokens'), ('*.raw',), 'Format tokens as a raw representation for storing token streams.'),
|
||||
'RtfFormatter': ('pygments.formatters.rtf', 'RTF', ('rtf',), ('*.rtf',), 'Format tokens as RTF markup. This formatter automatically outputs full RTF documents with color information and other useful stuff. Perfect for Copy and Paste into Microsoft(R) Word(R) documents.'),
|
||||
'SvgFormatter': ('pygments.formatters.svg', 'SVG', ('svg',), ('*.svg',), 'Format tokens as an SVG graphics file. This formatter is still experimental. Each line of code is a ``<text>`` element with explicit ``x`` and ``y`` coordinates containing ``<tspan>`` elements with the individual token styles.'),
|
||||
'Terminal256Formatter': ('pygments.formatters.terminal256', 'Terminal256', ('terminal256', 'console256', '256'), (), 'Format tokens with ANSI color sequences, for output in a 256-color terminal or console. Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly.'),
|
||||
'TerminalFormatter': ('pygments.formatters.terminal', 'Terminal', ('terminal', 'console'), (), 'Format tokens with ANSI color sequences, for output in a text console. Color sequences are terminated at newlines, so that paging the output works correctly.'),
|
||||
'TerminalTrueColorFormatter': ('pygments.formatters.terminal256', 'TerminalTrueColor', ('terminal16m', 'console16m', '16m'), (), 'Format tokens with ANSI color sequences, for output in a true-color terminal or console. Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly.'),
|
||||
'TestcaseFormatter': ('pygments.formatters.other', 'Testcase', ('testcase',), (), 'Format tokens as appropriate for a new testcase.'),
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
pygments.formatters.bbcode
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
BBcode formatter.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
from pip._vendor.pygments.util import get_bool_opt
|
||||
|
||||
__all__ = ['BBCodeFormatter']
|
||||
|
||||
|
||||
class BBCodeFormatter(Formatter):
|
||||
"""
|
||||
Format tokens with BBcodes. These formatting codes are used by many
|
||||
bulletin boards, so you can highlight your sourcecode with pygments before
|
||||
posting it there.
|
||||
|
||||
This formatter has no support for background colors and borders, as there
|
||||
are no common BBcode tags for that.
|
||||
|
||||
Some board systems (e.g. phpBB) don't support colors in their [code] tag,
|
||||
so you can't use the highlighting together with that tag.
|
||||
Text in a [code] tag usually is shown with a monospace font (which this
|
||||
formatter can do with the ``monofont`` option) and no spaces (which you
|
||||
need for indentation) are removed.
|
||||
|
||||
Additional options accepted:
|
||||
|
||||
`style`
|
||||
The style to use, can be a string or a Style subclass (default:
|
||||
``'default'``).
|
||||
|
||||
`codetag`
|
||||
If set to true, put the output into ``[code]`` tags (default:
|
||||
``false``)
|
||||
|
||||
`monofont`
|
||||
If set to true, add a tag to show the code with a monospace font
|
||||
(default: ``false``).
|
||||
"""
|
||||
name = 'BBCode'
|
||||
aliases = ['bbcode', 'bb']
|
||||
filenames = []
|
||||
|
||||
def __init__(self, **options):
|
||||
Formatter.__init__(self, **options)
|
||||
self._code = get_bool_opt(options, 'codetag', False)
|
||||
self._mono = get_bool_opt(options, 'monofont', False)
|
||||
|
||||
self.styles = {}
|
||||
self._make_styles()
|
||||
|
||||
def _make_styles(self):
|
||||
for ttype, ndef in self.style:
|
||||
start = end = ''
|
||||
if ndef['color']:
|
||||
start += '[color=#%s]' % ndef['color']
|
||||
end = '[/color]' + end
|
||||
if ndef['bold']:
|
||||
start += '[b]'
|
||||
end = '[/b]' + end
|
||||
if ndef['italic']:
|
||||
start += '[i]'
|
||||
end = '[/i]' + end
|
||||
if ndef['underline']:
|
||||
start += '[u]'
|
||||
end = '[/u]' + end
|
||||
# there are no common BBcodes for background-color and border
|
||||
|
||||
self.styles[ttype] = start, end
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
if self._code:
|
||||
outfile.write('[code]')
|
||||
if self._mono:
|
||||
outfile.write('[font=monospace]')
|
||||
|
||||
lastval = ''
|
||||
lasttype = None
|
||||
|
||||
for ttype, value in tokensource:
|
||||
while ttype not in self.styles:
|
||||
ttype = ttype.parent
|
||||
if ttype == lasttype:
|
||||
lastval += value
|
||||
else:
|
||||
if lastval:
|
||||
start, end = self.styles[lasttype]
|
||||
outfile.write(''.join((start, lastval, end)))
|
||||
lastval = value
|
||||
lasttype = ttype
|
||||
|
||||
if lastval:
|
||||
start, end = self.styles[lasttype]
|
||||
outfile.write(''.join((start, lastval, end)))
|
||||
|
||||
if self._mono:
|
||||
outfile.write('[/font]')
|
||||
if self._code:
|
||||
outfile.write('[/code]')
|
||||
if self._code or self._mono:
|
||||
outfile.write('\n')
|
||||
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
pygments.formatters.groff
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for groff output.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import math
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
from pip._vendor.pygments.util import get_bool_opt, get_int_opt
|
||||
|
||||
__all__ = ['GroffFormatter']
|
||||
|
||||
|
||||
class GroffFormatter(Formatter):
|
||||
"""
|
||||
Format tokens with groff escapes to change their color and font style.
|
||||
|
||||
.. versionadded:: 2.11
|
||||
|
||||
Additional options accepted:
|
||||
|
||||
`style`
|
||||
The style to use, can be a string or a Style subclass (default:
|
||||
``'default'``).
|
||||
|
||||
`monospaced`
|
||||
If set to true, monospace font will be used (default: ``true``).
|
||||
|
||||
`linenos`
|
||||
If set to true, print the line numbers (default: ``false``).
|
||||
|
||||
`wrap`
|
||||
Wrap lines to the specified number of characters. Disabled if set to 0
|
||||
(default: ``0``).
|
||||
"""
|
||||
|
||||
name = 'groff'
|
||||
aliases = ['groff','troff','roff']
|
||||
filenames = []
|
||||
|
||||
def __init__(self, **options):
|
||||
Formatter.__init__(self, **options)
|
||||
|
||||
self.monospaced = get_bool_opt(options, 'monospaced', True)
|
||||
self.linenos = get_bool_opt(options, 'linenos', False)
|
||||
self._lineno = 0
|
||||
self.wrap = get_int_opt(options, 'wrap', 0)
|
||||
self._linelen = 0
|
||||
|
||||
self.styles = {}
|
||||
self._make_styles()
|
||||
|
||||
|
||||
def _make_styles(self):
|
||||
regular = '\\f[CR]' if self.monospaced else '\\f[R]'
|
||||
bold = '\\f[CB]' if self.monospaced else '\\f[B]'
|
||||
italic = '\\f[CI]' if self.monospaced else '\\f[I]'
|
||||
|
||||
for ttype, ndef in self.style:
|
||||
start = end = ''
|
||||
if ndef['color']:
|
||||
start += '\\m[%s]' % ndef['color']
|
||||
end = '\\m[]' + end
|
||||
if ndef['bold']:
|
||||
start += bold
|
||||
end = regular + end
|
||||
if ndef['italic']:
|
||||
start += italic
|
||||
end = regular + end
|
||||
if ndef['bgcolor']:
|
||||
start += '\\M[%s]' % ndef['bgcolor']
|
||||
end = '\\M[]' + end
|
||||
|
||||
self.styles[ttype] = start, end
|
||||
|
||||
|
||||
def _define_colors(self, outfile):
|
||||
colors = set()
|
||||
for _, ndef in self.style:
|
||||
if ndef['color'] is not None:
|
||||
colors.add(ndef['color'])
|
||||
|
||||
for color in sorted(colors):
|
||||
outfile.write('.defcolor ' + color + ' rgb #' + color + '\n')
|
||||
|
||||
|
||||
def _write_lineno(self, outfile):
|
||||
self._lineno += 1
|
||||
outfile.write("%s% 4d " % (self._lineno != 1 and '\n' or '', self._lineno))
|
||||
|
||||
|
||||
def _wrap_line(self, line):
|
||||
length = len(line.rstrip('\n'))
|
||||
space = ' ' if self.linenos else ''
|
||||
newline = ''
|
||||
|
||||
if length > self.wrap:
|
||||
for i in range(0, math.floor(length / self.wrap)):
|
||||
chunk = line[i*self.wrap:i*self.wrap+self.wrap]
|
||||
newline += (chunk + '\n' + space)
|
||||
remainder = length % self.wrap
|
||||
if remainder > 0:
|
||||
newline += line[-remainder-1:]
|
||||
self._linelen = remainder
|
||||
elif self._linelen + length > self.wrap:
|
||||
newline = ('\n' + space) + line
|
||||
self._linelen = length
|
||||
else:
|
||||
newline = line
|
||||
self._linelen += length
|
||||
|
||||
return newline
|
||||
|
||||
|
||||
def _escape_chars(self, text):
|
||||
text = text.replace('\\', '\\[u005C]'). \
|
||||
replace('.', '\\[char46]'). \
|
||||
replace('\'', '\\[u0027]'). \
|
||||
replace('`', '\\[u0060]'). \
|
||||
replace('~', '\\[u007E]')
|
||||
copy = text
|
||||
|
||||
for char in copy:
|
||||
if len(char) != len(char.encode()):
|
||||
uni = char.encode('unicode_escape') \
|
||||
.decode()[1:] \
|
||||
.replace('x', 'u00') \
|
||||
.upper()
|
||||
text = text.replace(char, '\\[u' + uni[1:] + ']')
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
self._define_colors(outfile)
|
||||
|
||||
outfile.write('.nf\n\\f[CR]\n')
|
||||
|
||||
if self.linenos:
|
||||
self._write_lineno(outfile)
|
||||
|
||||
for ttype, value in tokensource:
|
||||
while ttype not in self.styles:
|
||||
ttype = ttype.parent
|
||||
start, end = self.styles[ttype]
|
||||
|
||||
for line in value.splitlines(True):
|
||||
if self.wrap > 0:
|
||||
line = self._wrap_line(line)
|
||||
|
||||
if start and end:
|
||||
text = self._escape_chars(line.rstrip('\n'))
|
||||
if text != '':
|
||||
outfile.write(''.join((start, text, end)))
|
||||
else:
|
||||
outfile.write(self._escape_chars(line.rstrip('\n')))
|
||||
|
||||
if line.endswith('\n'):
|
||||
if self.linenos:
|
||||
self._write_lineno(outfile)
|
||||
self._linelen = 0
|
||||
else:
|
||||
outfile.write('\n')
|
||||
self._linelen = 0
|
||||
|
||||
outfile.write('\n.fi')
|
||||
@@ -0,0 +1,989 @@
|
||||
"""
|
||||
pygments.formatters.html
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for HTML output.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import os
|
||||
import sys
|
||||
import os.path
|
||||
from io import StringIO
|
||||
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
from pip._vendor.pygments.token import Token, Text, STANDARD_TYPES
|
||||
from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt
|
||||
|
||||
try:
|
||||
import ctags
|
||||
except ImportError:
|
||||
ctags = None
|
||||
|
||||
__all__ = ['HtmlFormatter']
|
||||
|
||||
|
||||
_escape_html_table = {
|
||||
ord('&'): '&',
|
||||
ord('<'): '<',
|
||||
ord('>'): '>',
|
||||
ord('"'): '"',
|
||||
ord("'"): ''',
|
||||
}
|
||||
|
||||
|
||||
def escape_html(text, table=_escape_html_table):
|
||||
"""Escape &, <, > as well as single and double quotes for HTML."""
|
||||
return text.translate(table)
|
||||
|
||||
|
||||
def webify(color):
|
||||
if color.startswith('calc') or color.startswith('var'):
|
||||
return color
|
||||
else:
|
||||
return '#' + color
|
||||
|
||||
|
||||
def _get_ttype_class(ttype):
|
||||
fname = STANDARD_TYPES.get(ttype)
|
||||
if fname:
|
||||
return fname
|
||||
aname = ''
|
||||
while fname is None:
|
||||
aname = '-' + ttype[-1] + aname
|
||||
ttype = ttype.parent
|
||||
fname = STANDARD_TYPES.get(ttype)
|
||||
return fname + aname
|
||||
|
||||
|
||||
CSSFILE_TEMPLATE = '''\
|
||||
/*
|
||||
generated by Pygments <https://pygments.org/>
|
||||
Copyright 2006-2023 by the Pygments team.
|
||||
Licensed under the BSD license, see LICENSE for details.
|
||||
*/
|
||||
%(styledefs)s
|
||||
'''
|
||||
|
||||
DOC_HEADER = '''\
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
<!--
|
||||
generated by Pygments <https://pygments.org/>
|
||||
Copyright 2006-2023 by the Pygments team.
|
||||
Licensed under the BSD license, see LICENSE for details.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>%(title)s</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=%(encoding)s">
|
||||
<style type="text/css">
|
||||
''' + CSSFILE_TEMPLATE + '''
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>%(title)s</h2>
|
||||
|
||||
'''
|
||||
|
||||
DOC_HEADER_EXTERNALCSS = '''\
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>%(title)s</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=%(encoding)s">
|
||||
<link rel="stylesheet" href="%(cssfile)s" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<h2>%(title)s</h2>
|
||||
|
||||
'''
|
||||
|
||||
DOC_FOOTER = '''\
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
|
||||
class HtmlFormatter(Formatter):
|
||||
r"""
|
||||
Format tokens as HTML 4 ``<span>`` tags. By default, the content is enclosed
|
||||
in a ``<pre>`` tag, itself wrapped in a ``<div>`` tag (but see the `nowrap` option).
|
||||
The ``<div>``'s CSS class can be set by the `cssclass` option.
|
||||
|
||||
If the `linenos` option is set to ``"table"``, the ``<pre>`` is
|
||||
additionally wrapped inside a ``<table>`` which has one row and two
|
||||
cells: one containing the line numbers and one containing the code.
|
||||
Example:
|
||||
|
||||
.. sourcecode:: html
|
||||
|
||||
<div class="highlight" >
|
||||
<table><tr>
|
||||
<td class="linenos" title="click to toggle"
|
||||
onclick="with (this.firstChild.style)
|
||||
{ display = (display == '') ? 'none' : '' }">
|
||||
<pre>1
|
||||
2</pre>
|
||||
</td>
|
||||
<td class="code">
|
||||
<pre><span class="Ke">def </span><span class="NaFu">foo</span>(bar):
|
||||
<span class="Ke">pass</span>
|
||||
</pre>
|
||||
</td>
|
||||
</tr></table></div>
|
||||
|
||||
(whitespace added to improve clarity).
|
||||
|
||||
A list of lines can be specified using the `hl_lines` option to make these
|
||||
lines highlighted (as of Pygments 0.11).
|
||||
|
||||
With the `full` option, a complete HTML 4 document is output, including
|
||||
the style definitions inside a ``<style>`` tag, or in a separate file if
|
||||
the `cssfile` option is given.
|
||||
|
||||
When `tagsfile` is set to the path of a ctags index file, it is used to
|
||||
generate hyperlinks from names to their definition. You must enable
|
||||
`lineanchors` and run ctags with the `-n` option for this to work. The
|
||||
`python-ctags` module from PyPI must be installed to use this feature;
|
||||
otherwise a `RuntimeError` will be raised.
|
||||
|
||||
The `get_style_defs(arg='')` method of a `HtmlFormatter` returns a string
|
||||
containing CSS rules for the CSS classes used by the formatter. The
|
||||
argument `arg` can be used to specify additional CSS selectors that
|
||||
are prepended to the classes. A call `fmter.get_style_defs('td .code')`
|
||||
would result in the following CSS classes:
|
||||
|
||||
.. sourcecode:: css
|
||||
|
||||
td .code .kw { font-weight: bold; color: #00FF00 }
|
||||
td .code .cm { color: #999999 }
|
||||
...
|
||||
|
||||
If you have Pygments 0.6 or higher, you can also pass a list or tuple to the
|
||||
`get_style_defs()` method to request multiple prefixes for the tokens:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
formatter.get_style_defs(['div.syntax pre', 'pre.syntax'])
|
||||
|
||||
The output would then look like this:
|
||||
|
||||
.. sourcecode:: css
|
||||
|
||||
div.syntax pre .kw,
|
||||
pre.syntax .kw { font-weight: bold; color: #00FF00 }
|
||||
div.syntax pre .cm,
|
||||
pre.syntax .cm { color: #999999 }
|
||||
...
|
||||
|
||||
Additional options accepted:
|
||||
|
||||
`nowrap`
|
||||
If set to ``True``, don't add a ``<pre>`` and a ``<div>`` tag
|
||||
around the tokens. This disables most other options (default: ``False``).
|
||||
|
||||
`full`
|
||||
Tells the formatter to output a "full" document, i.e. a complete
|
||||
self-contained document (default: ``False``).
|
||||
|
||||
`title`
|
||||
If `full` is true, the title that should be used to caption the
|
||||
document (default: ``''``).
|
||||
|
||||
`style`
|
||||
The style to use, can be a string or a Style subclass (default:
|
||||
``'default'``). This option has no effect if the `cssfile`
|
||||
and `noclobber_cssfile` option are given and the file specified in
|
||||
`cssfile` exists.
|
||||
|
||||
`noclasses`
|
||||
If set to true, token ``<span>`` tags (as well as line number elements)
|
||||
will not use CSS classes, but inline styles. This is not recommended
|
||||
for larger pieces of code since it increases output size by quite a bit
|
||||
(default: ``False``).
|
||||
|
||||
`classprefix`
|
||||
Since the token types use relatively short class names, they may clash
|
||||
with some of your own class names. In this case you can use the
|
||||
`classprefix` option to give a string to prepend to all Pygments-generated
|
||||
CSS class names for token types.
|
||||
Note that this option also affects the output of `get_style_defs()`.
|
||||
|
||||
`cssclass`
|
||||
CSS class for the wrapping ``<div>`` tag (default: ``'highlight'``).
|
||||
If you set this option, the default selector for `get_style_defs()`
|
||||
will be this class.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
If you select the ``'table'`` line numbers, the wrapping table will
|
||||
have a CSS class of this string plus ``'table'``, the default is
|
||||
accordingly ``'highlighttable'``.
|
||||
|
||||
`cssstyles`
|
||||
Inline CSS styles for the wrapping ``<div>`` tag (default: ``''``).
|
||||
|
||||
`prestyles`
|
||||
Inline CSS styles for the ``<pre>`` tag (default: ``''``).
|
||||
|
||||
.. versionadded:: 0.11
|
||||
|
||||
`cssfile`
|
||||
If the `full` option is true and this option is given, it must be the
|
||||
name of an external file. If the filename does not include an absolute
|
||||
path, the file's path will be assumed to be relative to the main output
|
||||
file's path, if the latter can be found. The stylesheet is then written
|
||||
to this file instead of the HTML file.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
`noclobber_cssfile`
|
||||
If `cssfile` is given and the specified file exists, the css file will
|
||||
not be overwritten. This allows the use of the `full` option in
|
||||
combination with a user specified css file. Default is ``False``.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
`linenos`
|
||||
If set to ``'table'``, output line numbers as a table with two cells,
|
||||
one containing the line numbers, the other the whole code. This is
|
||||
copy-and-paste-friendly, but may cause alignment problems with some
|
||||
browsers or fonts. If set to ``'inline'``, the line numbers will be
|
||||
integrated in the ``<pre>`` tag that contains the code (that setting
|
||||
is *new in Pygments 0.8*).
|
||||
|
||||
For compatibility with Pygments 0.7 and earlier, every true value
|
||||
except ``'inline'`` means the same as ``'table'`` (in particular, that
|
||||
means also ``True``).
|
||||
|
||||
The default value is ``False``, which means no line numbers at all.
|
||||
|
||||
**Note:** with the default ("table") line number mechanism, the line
|
||||
numbers and code can have different line heights in Internet Explorer
|
||||
unless you give the enclosing ``<pre>`` tags an explicit ``line-height``
|
||||
CSS property (you get the default line spacing with ``line-height:
|
||||
125%``).
|
||||
|
||||
`hl_lines`
|
||||
Specify a list of lines to be highlighted. The line numbers are always
|
||||
relative to the input (i.e. the first line is line 1) and are
|
||||
independent of `linenostart`.
|
||||
|
||||
.. versionadded:: 0.11
|
||||
|
||||
`linenostart`
|
||||
The line number for the first line (default: ``1``).
|
||||
|
||||
`linenostep`
|
||||
If set to a number n > 1, only every nth line number is printed.
|
||||
|
||||
`linenospecial`
|
||||
If set to a number n > 0, every nth line number is given the CSS
|
||||
class ``"special"`` (default: ``0``).
|
||||
|
||||
`nobackground`
|
||||
If set to ``True``, the formatter won't output the background color
|
||||
for the wrapping element (this automatically defaults to ``False``
|
||||
when there is no wrapping element [eg: no argument for the
|
||||
`get_syntax_defs` method given]) (default: ``False``).
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
`lineseparator`
|
||||
This string is output between lines of code. It defaults to ``"\n"``,
|
||||
which is enough to break a line inside ``<pre>`` tags, but you can
|
||||
e.g. set it to ``"<br>"`` to get HTML line breaks.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
`lineanchors`
|
||||
If set to a nonempty string, e.g. ``foo``, the formatter will wrap each
|
||||
output line in an anchor tag with an ``id`` (and `name`) of ``foo-linenumber``.
|
||||
This allows easy linking to certain lines.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
|
||||
`linespans`
|
||||
If set to a nonempty string, e.g. ``foo``, the formatter will wrap each
|
||||
output line in a span tag with an ``id`` of ``foo-linenumber``.
|
||||
This allows easy access to lines via javascript.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
`anchorlinenos`
|
||||
If set to `True`, will wrap line numbers in <a> tags. Used in
|
||||
combination with `linenos` and `lineanchors`.
|
||||
|
||||
`tagsfile`
|
||||
If set to the path of a ctags file, wrap names in anchor tags that
|
||||
link to their definitions. `lineanchors` should be used, and the
|
||||
tags file should specify line numbers (see the `-n` option to ctags).
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
`tagurlformat`
|
||||
A string formatting pattern used to generate links to ctags definitions.
|
||||
Available variables are `%(path)s`, `%(fname)s` and `%(fext)s`.
|
||||
Defaults to an empty string, resulting in just `#prefix-number` links.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
`filename`
|
||||
A string used to generate a filename when rendering ``<pre>`` blocks,
|
||||
for example if displaying source code. If `linenos` is set to
|
||||
``'table'`` then the filename will be rendered in an initial row
|
||||
containing a single `<th>` which spans both columns.
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
`wrapcode`
|
||||
Wrap the code inside ``<pre>`` blocks using ``<code>``, as recommended
|
||||
by the HTML5 specification.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
`debug_token_types`
|
||||
Add ``title`` attributes to all token ``<span>`` tags that show the
|
||||
name of the token.
|
||||
|
||||
.. versionadded:: 2.10
|
||||
|
||||
|
||||
**Subclassing the HTML formatter**
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
The HTML formatter is now built in a way that allows easy subclassing, thus
|
||||
customizing the output HTML code. The `format()` method calls
|
||||
`self._format_lines()` which returns a generator that yields tuples of ``(1,
|
||||
line)``, where the ``1`` indicates that the ``line`` is a line of the
|
||||
formatted source code.
|
||||
|
||||
If the `nowrap` option is set, the generator is the iterated over and the
|
||||
resulting HTML is output.
|
||||
|
||||
Otherwise, `format()` calls `self.wrap()`, which wraps the generator with
|
||||
other generators. These may add some HTML code to the one generated by
|
||||
`_format_lines()`, either by modifying the lines generated by the latter,
|
||||
then yielding them again with ``(1, line)``, and/or by yielding other HTML
|
||||
code before or after the lines, with ``(0, html)``. The distinction between
|
||||
source lines and other code makes it possible to wrap the generator multiple
|
||||
times.
|
||||
|
||||
The default `wrap()` implementation adds a ``<div>`` and a ``<pre>`` tag.
|
||||
|
||||
A custom `HtmlFormatter` subclass could look like this:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class CodeHtmlFormatter(HtmlFormatter):
|
||||
|
||||
def wrap(self, source, *, include_div):
|
||||
return self._wrap_code(source)
|
||||
|
||||
def _wrap_code(self, source):
|
||||
yield 0, '<code>'
|
||||
for i, t in source:
|
||||
if i == 1:
|
||||
# it's a line of formatted code
|
||||
t += '<br>'
|
||||
yield i, t
|
||||
yield 0, '</code>'
|
||||
|
||||
This results in wrapping the formatted lines with a ``<code>`` tag, where the
|
||||
source lines are broken using ``<br>`` tags.
|
||||
|
||||
After calling `wrap()`, the `format()` method also adds the "line numbers"
|
||||
and/or "full document" wrappers if the respective options are set. Then, all
|
||||
HTML yielded by the wrapped generator is output.
|
||||
"""
|
||||
|
||||
name = 'HTML'
|
||||
aliases = ['html']
|
||||
filenames = ['*.html', '*.htm']
|
||||
|
||||
def __init__(self, **options):
|
||||
Formatter.__init__(self, **options)
|
||||
self.title = self._decodeifneeded(self.title)
|
||||
self.nowrap = get_bool_opt(options, 'nowrap', False)
|
||||
self.noclasses = get_bool_opt(options, 'noclasses', False)
|
||||
self.classprefix = options.get('classprefix', '')
|
||||
self.cssclass = self._decodeifneeded(options.get('cssclass', 'highlight'))
|
||||
self.cssstyles = self._decodeifneeded(options.get('cssstyles', ''))
|
||||
self.prestyles = self._decodeifneeded(options.get('prestyles', ''))
|
||||
self.cssfile = self._decodeifneeded(options.get('cssfile', ''))
|
||||
self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False)
|
||||
self.tagsfile = self._decodeifneeded(options.get('tagsfile', ''))
|
||||
self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', ''))
|
||||
self.filename = self._decodeifneeded(options.get('filename', ''))
|
||||
self.wrapcode = get_bool_opt(options, 'wrapcode', False)
|
||||
self.span_element_openers = {}
|
||||
self.debug_token_types = get_bool_opt(options, 'debug_token_types', False)
|
||||
|
||||
if self.tagsfile:
|
||||
if not ctags:
|
||||
raise RuntimeError('The "ctags" package must to be installed '
|
||||
'to be able to use the "tagsfile" feature.')
|
||||
self._ctags = ctags.CTags(self.tagsfile)
|
||||
|
||||
linenos = options.get('linenos', False)
|
||||
if linenos == 'inline':
|
||||
self.linenos = 2
|
||||
elif linenos:
|
||||
# compatibility with <= 0.7
|
||||
self.linenos = 1
|
||||
else:
|
||||
self.linenos = 0
|
||||
self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
|
||||
self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
|
||||
self.linenospecial = abs(get_int_opt(options, 'linenospecial', 0))
|
||||
self.nobackground = get_bool_opt(options, 'nobackground', False)
|
||||
self.lineseparator = options.get('lineseparator', '\n')
|
||||
self.lineanchors = options.get('lineanchors', '')
|
||||
self.linespans = options.get('linespans', '')
|
||||
self.anchorlinenos = get_bool_opt(options, 'anchorlinenos', False)
|
||||
self.hl_lines = set()
|
||||
for lineno in get_list_opt(options, 'hl_lines', []):
|
||||
try:
|
||||
self.hl_lines.add(int(lineno))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
self._create_stylesheet()
|
||||
|
||||
def _get_css_class(self, ttype):
|
||||
"""Return the css class of this token type prefixed with
|
||||
the classprefix option."""
|
||||
ttypeclass = _get_ttype_class(ttype)
|
||||
if ttypeclass:
|
||||
return self.classprefix + ttypeclass
|
||||
return ''
|
||||
|
||||
def _get_css_classes(self, ttype):
|
||||
"""Return the CSS classes of this token type prefixed with the classprefix option."""
|
||||
cls = self._get_css_class(ttype)
|
||||
while ttype not in STANDARD_TYPES:
|
||||
ttype = ttype.parent
|
||||
cls = self._get_css_class(ttype) + ' ' + cls
|
||||
return cls or ''
|
||||
|
||||
def _get_css_inline_styles(self, ttype):
|
||||
"""Return the inline CSS styles for this token type."""
|
||||
cclass = self.ttype2class.get(ttype)
|
||||
while cclass is None:
|
||||
ttype = ttype.parent
|
||||
cclass = self.ttype2class.get(ttype)
|
||||
return cclass or ''
|
||||
|
||||
def _create_stylesheet(self):
|
||||
t2c = self.ttype2class = {Token: ''}
|
||||
c2s = self.class2style = {}
|
||||
for ttype, ndef in self.style:
|
||||
name = self._get_css_class(ttype)
|
||||
style = ''
|
||||
if ndef['color']:
|
||||
style += 'color: %s; ' % webify(ndef['color'])
|
||||
if ndef['bold']:
|
||||
style += 'font-weight: bold; '
|
||||
if ndef['italic']:
|
||||
style += 'font-style: italic; '
|
||||
if ndef['underline']:
|
||||
style += 'text-decoration: underline; '
|
||||
if ndef['bgcolor']:
|
||||
style += 'background-color: %s; ' % webify(ndef['bgcolor'])
|
||||
if ndef['border']:
|
||||
style += 'border: 1px solid %s; ' % webify(ndef['border'])
|
||||
if style:
|
||||
t2c[ttype] = name
|
||||
# save len(ttype) to enable ordering the styles by
|
||||
# hierarchy (necessary for CSS cascading rules!)
|
||||
c2s[name] = (style[:-2], ttype, len(ttype))
|
||||
|
||||
def get_style_defs(self, arg=None):
|
||||
"""
|
||||
Return CSS style definitions for the classes produced by the current
|
||||
highlighting style. ``arg`` can be a string or list of selectors to
|
||||
insert before the token type classes.
|
||||
"""
|
||||
style_lines = []
|
||||
|
||||
style_lines.extend(self.get_linenos_style_defs())
|
||||
style_lines.extend(self.get_background_style_defs(arg))
|
||||
style_lines.extend(self.get_token_style_defs(arg))
|
||||
|
||||
return '\n'.join(style_lines)
|
||||
|
||||
def get_token_style_defs(self, arg=None):
|
||||
prefix = self.get_css_prefix(arg)
|
||||
|
||||
styles = [
|
||||
(level, ttype, cls, style)
|
||||
for cls, (style, ttype, level) in self.class2style.items()
|
||||
if cls and style
|
||||
]
|
||||
styles.sort()
|
||||
|
||||
lines = [
|
||||
'%s { %s } /* %s */' % (prefix(cls), style, repr(ttype)[6:])
|
||||
for (level, ttype, cls, style) in styles
|
||||
]
|
||||
|
||||
return lines
|
||||
|
||||
def get_background_style_defs(self, arg=None):
|
||||
prefix = self.get_css_prefix(arg)
|
||||
bg_color = self.style.background_color
|
||||
hl_color = self.style.highlight_color
|
||||
|
||||
lines = []
|
||||
|
||||
if arg and not self.nobackground and bg_color is not None:
|
||||
text_style = ''
|
||||
if Text in self.ttype2class:
|
||||
text_style = ' ' + self.class2style[self.ttype2class[Text]][0]
|
||||
lines.insert(
|
||||
0, '%s{ background: %s;%s }' % (
|
||||
prefix(''), bg_color, text_style
|
||||
)
|
||||
)
|
||||
if hl_color is not None:
|
||||
lines.insert(
|
||||
0, '%s { background-color: %s }' % (prefix('hll'), hl_color)
|
||||
)
|
||||
|
||||
return lines
|
||||
|
||||
def get_linenos_style_defs(self):
|
||||
lines = [
|
||||
'pre { %s }' % self._pre_style,
|
||||
'td.linenos .normal { %s }' % self._linenos_style,
|
||||
'span.linenos { %s }' % self._linenos_style,
|
||||
'td.linenos .special { %s }' % self._linenos_special_style,
|
||||
'span.linenos.special { %s }' % self._linenos_special_style,
|
||||
]
|
||||
|
||||
return lines
|
||||
|
||||
def get_css_prefix(self, arg):
|
||||
if arg is None:
|
||||
arg = ('cssclass' in self.options and '.'+self.cssclass or '')
|
||||
if isinstance(arg, str):
|
||||
args = [arg]
|
||||
else:
|
||||
args = list(arg)
|
||||
|
||||
def prefix(cls):
|
||||
if cls:
|
||||
cls = '.' + cls
|
||||
tmp = []
|
||||
for arg in args:
|
||||
tmp.append((arg and arg + ' ' or '') + cls)
|
||||
return ', '.join(tmp)
|
||||
|
||||
return prefix
|
||||
|
||||
@property
|
||||
def _pre_style(self):
|
||||
return 'line-height: 125%;'
|
||||
|
||||
@property
|
||||
def _linenos_style(self):
|
||||
return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % (
|
||||
self.style.line_number_color,
|
||||
self.style.line_number_background_color
|
||||
)
|
||||
|
||||
@property
|
||||
def _linenos_special_style(self):
|
||||
return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % (
|
||||
self.style.line_number_special_color,
|
||||
self.style.line_number_special_background_color
|
||||
)
|
||||
|
||||
def _decodeifneeded(self, value):
|
||||
if isinstance(value, bytes):
|
||||
if self.encoding:
|
||||
return value.decode(self.encoding)
|
||||
return value.decode()
|
||||
return value
|
||||
|
||||
def _wrap_full(self, inner, outfile):
|
||||
if self.cssfile:
|
||||
if os.path.isabs(self.cssfile):
|
||||
# it's an absolute filename
|
||||
cssfilename = self.cssfile
|
||||
else:
|
||||
try:
|
||||
filename = outfile.name
|
||||
if not filename or filename[0] == '<':
|
||||
# pseudo files, e.g. name == '<fdopen>'
|
||||
raise AttributeError
|
||||
cssfilename = os.path.join(os.path.dirname(filename),
|
||||
self.cssfile)
|
||||
except AttributeError:
|
||||
print('Note: Cannot determine output file name, '
|
||||
'using current directory as base for the CSS file name',
|
||||
file=sys.stderr)
|
||||
cssfilename = self.cssfile
|
||||
# write CSS file only if noclobber_cssfile isn't given as an option.
|
||||
try:
|
||||
if not os.path.exists(cssfilename) or not self.noclobber_cssfile:
|
||||
with open(cssfilename, "w", encoding="utf-8") as cf:
|
||||
cf.write(CSSFILE_TEMPLATE %
|
||||
{'styledefs': self.get_style_defs('body')})
|
||||
except OSError as err:
|
||||
err.strerror = 'Error writing CSS file: ' + err.strerror
|
||||
raise
|
||||
|
||||
yield 0, (DOC_HEADER_EXTERNALCSS %
|
||||
dict(title=self.title,
|
||||
cssfile=self.cssfile,
|
||||
encoding=self.encoding))
|
||||
else:
|
||||
yield 0, (DOC_HEADER %
|
||||
dict(title=self.title,
|
||||
styledefs=self.get_style_defs('body'),
|
||||
encoding=self.encoding))
|
||||
|
||||
yield from inner
|
||||
yield 0, DOC_FOOTER
|
||||
|
||||
def _wrap_tablelinenos(self, inner):
|
||||
dummyoutfile = StringIO()
|
||||
lncount = 0
|
||||
for t, line in inner:
|
||||
if t:
|
||||
lncount += 1
|
||||
dummyoutfile.write(line)
|
||||
|
||||
fl = self.linenostart
|
||||
mw = len(str(lncount + fl - 1))
|
||||
sp = self.linenospecial
|
||||
st = self.linenostep
|
||||
anchor_name = self.lineanchors or self.linespans
|
||||
aln = self.anchorlinenos
|
||||
nocls = self.noclasses
|
||||
|
||||
lines = []
|
||||
|
||||
for i in range(fl, fl+lncount):
|
||||
print_line = i % st == 0
|
||||
special_line = sp and i % sp == 0
|
||||
|
||||
if print_line:
|
||||
line = '%*d' % (mw, i)
|
||||
if aln:
|
||||
line = '<a href="#%s-%d">%s</a>' % (anchor_name, i, line)
|
||||
else:
|
||||
line = ' ' * mw
|
||||
|
||||
if nocls:
|
||||
if special_line:
|
||||
style = ' style="%s"' % self._linenos_special_style
|
||||
else:
|
||||
style = ' style="%s"' % self._linenos_style
|
||||
else:
|
||||
if special_line:
|
||||
style = ' class="special"'
|
||||
else:
|
||||
style = ' class="normal"'
|
||||
|
||||
if style:
|
||||
line = '<span%s>%s</span>' % (style, line)
|
||||
|
||||
lines.append(line)
|
||||
|
||||
ls = '\n'.join(lines)
|
||||
|
||||
# If a filename was specified, we can't put it into the code table as it
|
||||
# would misalign the line numbers. Hence we emit a separate row for it.
|
||||
filename_tr = ""
|
||||
if self.filename:
|
||||
filename_tr = (
|
||||
'<tr><th colspan="2" class="filename">'
|
||||
'<span class="filename">' + self.filename + '</span>'
|
||||
'</th></tr>')
|
||||
|
||||
# in case you wonder about the seemingly redundant <div> here: since the
|
||||
# content in the other cell also is wrapped in a div, some browsers in
|
||||
# some configurations seem to mess up the formatting...
|
||||
yield 0, (f'<table class="{self.cssclass}table">' + filename_tr +
|
||||
'<tr><td class="linenos"><div class="linenodiv"><pre>' +
|
||||
ls + '</pre></div></td><td class="code">')
|
||||
yield 0, '<div>'
|
||||
yield 0, dummyoutfile.getvalue()
|
||||
yield 0, '</div>'
|
||||
yield 0, '</td></tr></table>'
|
||||
|
||||
|
||||
def _wrap_inlinelinenos(self, inner):
|
||||
# need a list of lines since we need the width of a single number :(
|
||||
inner_lines = list(inner)
|
||||
sp = self.linenospecial
|
||||
st = self.linenostep
|
||||
num = self.linenostart
|
||||
mw = len(str(len(inner_lines) + num - 1))
|
||||
anchor_name = self.lineanchors or self.linespans
|
||||
aln = self.anchorlinenos
|
||||
nocls = self.noclasses
|
||||
|
||||
for _, inner_line in inner_lines:
|
||||
print_line = num % st == 0
|
||||
special_line = sp and num % sp == 0
|
||||
|
||||
if print_line:
|
||||
line = '%*d' % (mw, num)
|
||||
else:
|
||||
line = ' ' * mw
|
||||
|
||||
if nocls:
|
||||
if special_line:
|
||||
style = ' style="%s"' % self._linenos_special_style
|
||||
else:
|
||||
style = ' style="%s"' % self._linenos_style
|
||||
else:
|
||||
if special_line:
|
||||
style = ' class="linenos special"'
|
||||
else:
|
||||
style = ' class="linenos"'
|
||||
|
||||
if style:
|
||||
linenos = '<span%s>%s</span>' % (style, line)
|
||||
else:
|
||||
linenos = line
|
||||
|
||||
if aln:
|
||||
yield 1, ('<a href="#%s-%d">%s</a>' % (anchor_name, num, linenos) +
|
||||
inner_line)
|
||||
else:
|
||||
yield 1, linenos + inner_line
|
||||
num += 1
|
||||
|
||||
def _wrap_lineanchors(self, inner):
|
||||
s = self.lineanchors
|
||||
# subtract 1 since we have to increment i *before* yielding
|
||||
i = self.linenostart - 1
|
||||
for t, line in inner:
|
||||
if t:
|
||||
i += 1
|
||||
href = "" if self.linenos else ' href="#%s-%d"' % (s, i)
|
||||
yield 1, '<a id="%s-%d" name="%s-%d"%s></a>' % (s, i, s, i, href) + line
|
||||
else:
|
||||
yield 0, line
|
||||
|
||||
def _wrap_linespans(self, inner):
|
||||
s = self.linespans
|
||||
i = self.linenostart - 1
|
||||
for t, line in inner:
|
||||
if t:
|
||||
i += 1
|
||||
yield 1, '<span id="%s-%d">%s</span>' % (s, i, line)
|
||||
else:
|
||||
yield 0, line
|
||||
|
||||
def _wrap_div(self, inner):
|
||||
style = []
|
||||
if (self.noclasses and not self.nobackground and
|
||||
self.style.background_color is not None):
|
||||
style.append('background: %s' % (self.style.background_color,))
|
||||
if self.cssstyles:
|
||||
style.append(self.cssstyles)
|
||||
style = '; '.join(style)
|
||||
|
||||
yield 0, ('<div' + (self.cssclass and ' class="%s"' % self.cssclass) +
|
||||
(style and (' style="%s"' % style)) + '>')
|
||||
yield from inner
|
||||
yield 0, '</div>\n'
|
||||
|
||||
def _wrap_pre(self, inner):
|
||||
style = []
|
||||
if self.prestyles:
|
||||
style.append(self.prestyles)
|
||||
if self.noclasses:
|
||||
style.append(self._pre_style)
|
||||
style = '; '.join(style)
|
||||
|
||||
if self.filename and self.linenos != 1:
|
||||
yield 0, ('<span class="filename">' + self.filename + '</span>')
|
||||
|
||||
# the empty span here is to keep leading empty lines from being
|
||||
# ignored by HTML parsers
|
||||
yield 0, ('<pre' + (style and ' style="%s"' % style) + '><span></span>')
|
||||
yield from inner
|
||||
yield 0, '</pre>'
|
||||
|
||||
def _wrap_code(self, inner):
|
||||
yield 0, '<code>'
|
||||
yield from inner
|
||||
yield 0, '</code>'
|
||||
|
||||
@functools.lru_cache(maxsize=100)
|
||||
def _translate_parts(self, value):
|
||||
"""HTML-escape a value and split it by newlines."""
|
||||
return value.translate(_escape_html_table).split('\n')
|
||||
|
||||
def _format_lines(self, tokensource):
|
||||
"""
|
||||
Just format the tokens, without any wrapping tags.
|
||||
Yield individual lines.
|
||||
"""
|
||||
nocls = self.noclasses
|
||||
lsep = self.lineseparator
|
||||
tagsfile = self.tagsfile
|
||||
|
||||
lspan = ''
|
||||
line = []
|
||||
for ttype, value in tokensource:
|
||||
try:
|
||||
cspan = self.span_element_openers[ttype]
|
||||
except KeyError:
|
||||
title = ' title="%s"' % '.'.join(ttype) if self.debug_token_types else ''
|
||||
if nocls:
|
||||
css_style = self._get_css_inline_styles(ttype)
|
||||
if css_style:
|
||||
css_style = self.class2style[css_style][0]
|
||||
cspan = '<span style="%s"%s>' % (css_style, title)
|
||||
else:
|
||||
cspan = ''
|
||||
else:
|
||||
css_class = self._get_css_classes(ttype)
|
||||
if css_class:
|
||||
cspan = '<span class="%s"%s>' % (css_class, title)
|
||||
else:
|
||||
cspan = ''
|
||||
self.span_element_openers[ttype] = cspan
|
||||
|
||||
parts = self._translate_parts(value)
|
||||
|
||||
if tagsfile and ttype in Token.Name:
|
||||
filename, linenumber = self._lookup_ctag(value)
|
||||
if linenumber:
|
||||
base, filename = os.path.split(filename)
|
||||
if base:
|
||||
base += '/'
|
||||
filename, extension = os.path.splitext(filename)
|
||||
url = self.tagurlformat % {'path': base, 'fname': filename,
|
||||
'fext': extension}
|
||||
parts[0] = "<a href=\"%s#%s-%d\">%s" % \
|
||||
(url, self.lineanchors, linenumber, parts[0])
|
||||
parts[-1] = parts[-1] + "</a>"
|
||||
|
||||
# for all but the last line
|
||||
for part in parts[:-1]:
|
||||
if line:
|
||||
# Also check for part being non-empty, so we avoid creating
|
||||
# empty <span> tags
|
||||
if lspan != cspan and part:
|
||||
line.extend(((lspan and '</span>'), cspan, part,
|
||||
(cspan and '</span>'), lsep))
|
||||
else: # both are the same, or the current part was empty
|
||||
line.extend((part, (lspan and '</span>'), lsep))
|
||||
yield 1, ''.join(line)
|
||||
line = []
|
||||
elif part:
|
||||
yield 1, ''.join((cspan, part, (cspan and '</span>'), lsep))
|
||||
else:
|
||||
yield 1, lsep
|
||||
# for the last line
|
||||
if line and parts[-1]:
|
||||
if lspan != cspan:
|
||||
line.extend(((lspan and '</span>'), cspan, parts[-1]))
|
||||
lspan = cspan
|
||||
else:
|
||||
line.append(parts[-1])
|
||||
elif parts[-1]:
|
||||
line = [cspan, parts[-1]]
|
||||
lspan = cspan
|
||||
# else we neither have to open a new span nor set lspan
|
||||
|
||||
if line:
|
||||
line.extend(((lspan and '</span>'), lsep))
|
||||
yield 1, ''.join(line)
|
||||
|
||||
def _lookup_ctag(self, token):
|
||||
entry = ctags.TagEntry()
|
||||
if self._ctags.find(entry, token.encode(), 0):
|
||||
return entry['file'], entry['lineNumber']
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def _highlight_lines(self, tokensource):
|
||||
"""
|
||||
Highlighted the lines specified in the `hl_lines` option by
|
||||
post-processing the token stream coming from `_format_lines`.
|
||||
"""
|
||||
hls = self.hl_lines
|
||||
|
||||
for i, (t, value) in enumerate(tokensource):
|
||||
if t != 1:
|
||||
yield t, value
|
||||
if i + 1 in hls: # i + 1 because Python indexes start at 0
|
||||
if self.noclasses:
|
||||
style = ''
|
||||
if self.style.highlight_color is not None:
|
||||
style = (' style="background-color: %s"' %
|
||||
(self.style.highlight_color,))
|
||||
yield 1, '<span%s>%s</span>' % (style, value)
|
||||
else:
|
||||
yield 1, '<span class="hll">%s</span>' % value
|
||||
else:
|
||||
yield 1, value
|
||||
|
||||
def wrap(self, source):
|
||||
"""
|
||||
Wrap the ``source``, which is a generator yielding
|
||||
individual lines, in custom generators. See docstring
|
||||
for `format`. Can be overridden.
|
||||
"""
|
||||
|
||||
output = source
|
||||
if self.wrapcode:
|
||||
output = self._wrap_code(output)
|
||||
|
||||
output = self._wrap_pre(output)
|
||||
|
||||
return output
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
"""
|
||||
The formatting process uses several nested generators; which of
|
||||
them are used is determined by the user's options.
|
||||
|
||||
Each generator should take at least one argument, ``inner``,
|
||||
and wrap the pieces of text generated by this.
|
||||
|
||||
Always yield 2-tuples: (code, text). If "code" is 1, the text
|
||||
is part of the original tokensource being highlighted, if it's
|
||||
0, the text is some piece of wrapping. This makes it possible to
|
||||
use several different wrappers that process the original source
|
||||
linewise, e.g. line number generators.
|
||||
"""
|
||||
source = self._format_lines(tokensource)
|
||||
|
||||
# As a special case, we wrap line numbers before line highlighting
|
||||
# so the line numbers get wrapped in the highlighting tag.
|
||||
if not self.nowrap and self.linenos == 2:
|
||||
source = self._wrap_inlinelinenos(source)
|
||||
|
||||
if self.hl_lines:
|
||||
source = self._highlight_lines(source)
|
||||
|
||||
if not self.nowrap:
|
||||
if self.lineanchors:
|
||||
source = self._wrap_lineanchors(source)
|
||||
if self.linespans:
|
||||
source = self._wrap_linespans(source)
|
||||
source = self.wrap(source)
|
||||
if self.linenos == 1:
|
||||
source = self._wrap_tablelinenos(source)
|
||||
source = self._wrap_div(source)
|
||||
if self.full:
|
||||
source = self._wrap_full(source, outfile)
|
||||
|
||||
for t, piece in source:
|
||||
outfile.write(piece)
|
||||
@@ -0,0 +1,645 @@
|
||||
"""
|
||||
pygments.formatters.img
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for Pixmap output.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt, \
|
||||
get_choice_opt
|
||||
|
||||
import subprocess
|
||||
|
||||
# Import this carefully
|
||||
try:
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
pil_available = True
|
||||
except ImportError:
|
||||
pil_available = False
|
||||
|
||||
try:
|
||||
import _winreg
|
||||
except ImportError:
|
||||
try:
|
||||
import winreg as _winreg
|
||||
except ImportError:
|
||||
_winreg = None
|
||||
|
||||
__all__ = ['ImageFormatter', 'GifImageFormatter', 'JpgImageFormatter',
|
||||
'BmpImageFormatter']
|
||||
|
||||
|
||||
# For some unknown reason every font calls it something different
|
||||
STYLES = {
|
||||
'NORMAL': ['', 'Roman', 'Book', 'Normal', 'Regular', 'Medium'],
|
||||
'ITALIC': ['Oblique', 'Italic'],
|
||||
'BOLD': ['Bold'],
|
||||
'BOLDITALIC': ['Bold Oblique', 'Bold Italic'],
|
||||
}
|
||||
|
||||
# A sane default for modern systems
|
||||
DEFAULT_FONT_NAME_NIX = 'DejaVu Sans Mono'
|
||||
DEFAULT_FONT_NAME_WIN = 'Courier New'
|
||||
DEFAULT_FONT_NAME_MAC = 'Menlo'
|
||||
|
||||
|
||||
class PilNotAvailable(ImportError):
|
||||
"""When Python imaging library is not available"""
|
||||
|
||||
|
||||
class FontNotFound(Exception):
|
||||
"""When there are no usable fonts specified"""
|
||||
|
||||
|
||||
class FontManager:
|
||||
"""
|
||||
Manages a set of fonts: normal, italic, bold, etc...
|
||||
"""
|
||||
|
||||
def __init__(self, font_name, font_size=14):
|
||||
self.font_name = font_name
|
||||
self.font_size = font_size
|
||||
self.fonts = {}
|
||||
self.encoding = None
|
||||
if sys.platform.startswith('win'):
|
||||
if not font_name:
|
||||
self.font_name = DEFAULT_FONT_NAME_WIN
|
||||
self._create_win()
|
||||
elif sys.platform.startswith('darwin'):
|
||||
if not font_name:
|
||||
self.font_name = DEFAULT_FONT_NAME_MAC
|
||||
self._create_mac()
|
||||
else:
|
||||
if not font_name:
|
||||
self.font_name = DEFAULT_FONT_NAME_NIX
|
||||
self._create_nix()
|
||||
|
||||
def _get_nix_font_path(self, name, style):
|
||||
proc = subprocess.Popen(['fc-list', "%s:style=%s" % (name, style), 'file'],
|
||||
stdout=subprocess.PIPE, stderr=None)
|
||||
stdout, _ = proc.communicate()
|
||||
if proc.returncode == 0:
|
||||
lines = stdout.splitlines()
|
||||
for line in lines:
|
||||
if line.startswith(b'Fontconfig warning:'):
|
||||
continue
|
||||
path = line.decode().strip().strip(':')
|
||||
if path:
|
||||
return path
|
||||
return None
|
||||
|
||||
def _create_nix(self):
|
||||
for name in STYLES['NORMAL']:
|
||||
path = self._get_nix_font_path(self.font_name, name)
|
||||
if path is not None:
|
||||
self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
|
||||
break
|
||||
else:
|
||||
raise FontNotFound('No usable fonts named: "%s"' %
|
||||
self.font_name)
|
||||
for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
|
||||
for stylename in STYLES[style]:
|
||||
path = self._get_nix_font_path(self.font_name, stylename)
|
||||
if path is not None:
|
||||
self.fonts[style] = ImageFont.truetype(path, self.font_size)
|
||||
break
|
||||
else:
|
||||
if style == 'BOLDITALIC':
|
||||
self.fonts[style] = self.fonts['BOLD']
|
||||
else:
|
||||
self.fonts[style] = self.fonts['NORMAL']
|
||||
|
||||
def _get_mac_font_path(self, font_map, name, style):
|
||||
return font_map.get((name + ' ' + style).strip().lower())
|
||||
|
||||
def _create_mac(self):
|
||||
font_map = {}
|
||||
for font_dir in (os.path.join(os.getenv("HOME"), 'Library/Fonts/'),
|
||||
'/Library/Fonts/', '/System/Library/Fonts/'):
|
||||
font_map.update(
|
||||
(os.path.splitext(f)[0].lower(), os.path.join(font_dir, f))
|
||||
for f in os.listdir(font_dir)
|
||||
if f.lower().endswith(('ttf', 'ttc')))
|
||||
|
||||
for name in STYLES['NORMAL']:
|
||||
path = self._get_mac_font_path(font_map, self.font_name, name)
|
||||
if path is not None:
|
||||
self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
|
||||
break
|
||||
else:
|
||||
raise FontNotFound('No usable fonts named: "%s"' %
|
||||
self.font_name)
|
||||
for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
|
||||
for stylename in STYLES[style]:
|
||||
path = self._get_mac_font_path(font_map, self.font_name, stylename)
|
||||
if path is not None:
|
||||
self.fonts[style] = ImageFont.truetype(path, self.font_size)
|
||||
break
|
||||
else:
|
||||
if style == 'BOLDITALIC':
|
||||
self.fonts[style] = self.fonts['BOLD']
|
||||
else:
|
||||
self.fonts[style] = self.fonts['NORMAL']
|
||||
|
||||
def _lookup_win(self, key, basename, styles, fail=False):
|
||||
for suffix in ('', ' (TrueType)'):
|
||||
for style in styles:
|
||||
try:
|
||||
valname = '%s%s%s' % (basename, style and ' '+style, suffix)
|
||||
val, _ = _winreg.QueryValueEx(key, valname)
|
||||
return val
|
||||
except OSError:
|
||||
continue
|
||||
else:
|
||||
if fail:
|
||||
raise FontNotFound('Font %s (%s) not found in registry' %
|
||||
(basename, styles[0]))
|
||||
return None
|
||||
|
||||
def _create_win(self):
|
||||
lookuperror = None
|
||||
keynames = [ (_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows NT\CurrentVersion\Fonts'),
|
||||
(_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Fonts'),
|
||||
(_winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows NT\CurrentVersion\Fonts'),
|
||||
(_winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows\CurrentVersion\Fonts') ]
|
||||
for keyname in keynames:
|
||||
try:
|
||||
key = _winreg.OpenKey(*keyname)
|
||||
try:
|
||||
path = self._lookup_win(key, self.font_name, STYLES['NORMAL'], True)
|
||||
self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
|
||||
for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
|
||||
path = self._lookup_win(key, self.font_name, STYLES[style])
|
||||
if path:
|
||||
self.fonts[style] = ImageFont.truetype(path, self.font_size)
|
||||
else:
|
||||
if style == 'BOLDITALIC':
|
||||
self.fonts[style] = self.fonts['BOLD']
|
||||
else:
|
||||
self.fonts[style] = self.fonts['NORMAL']
|
||||
return
|
||||
except FontNotFound as err:
|
||||
lookuperror = err
|
||||
finally:
|
||||
_winreg.CloseKey(key)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
# If we get here, we checked all registry keys and had no luck
|
||||
# We can be in one of two situations now:
|
||||
# * All key lookups failed. In this case lookuperror is None and we
|
||||
# will raise a generic error
|
||||
# * At least one lookup failed with a FontNotFound error. In this
|
||||
# case, we will raise that as a more specific error
|
||||
if lookuperror:
|
||||
raise lookuperror
|
||||
raise FontNotFound('Can\'t open Windows font registry key')
|
||||
|
||||
def get_char_size(self):
|
||||
"""
|
||||
Get the character size.
|
||||
"""
|
||||
return self.get_text_size('M')
|
||||
|
||||
def get_text_size(self, text):
|
||||
"""
|
||||
Get the text size (width, height).
|
||||
"""
|
||||
font = self.fonts['NORMAL']
|
||||
if hasattr(font, 'getbbox'): # Pillow >= 9.2.0
|
||||
return font.getbbox(text)[2:4]
|
||||
else:
|
||||
return font.getsize(text)
|
||||
|
||||
def get_font(self, bold, oblique):
|
||||
"""
|
||||
Get the font based on bold and italic flags.
|
||||
"""
|
||||
if bold and oblique:
|
||||
return self.fonts['BOLDITALIC']
|
||||
elif bold:
|
||||
return self.fonts['BOLD']
|
||||
elif oblique:
|
||||
return self.fonts['ITALIC']
|
||||
else:
|
||||
return self.fonts['NORMAL']
|
||||
|
||||
|
||||
class ImageFormatter(Formatter):
|
||||
"""
|
||||
Create a PNG image from source code. This uses the Python Imaging Library to
|
||||
generate a pixmap from the source code.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
Additional options accepted:
|
||||
|
||||
`image_format`
|
||||
An image format to output to that is recognised by PIL, these include:
|
||||
|
||||
* "PNG" (default)
|
||||
* "JPEG"
|
||||
* "BMP"
|
||||
* "GIF"
|
||||
|
||||
`line_pad`
|
||||
The extra spacing (in pixels) between each line of text.
|
||||
|
||||
Default: 2
|
||||
|
||||
`font_name`
|
||||
The font name to be used as the base font from which others, such as
|
||||
bold and italic fonts will be generated. This really should be a
|
||||
monospace font to look sane.
|
||||
|
||||
Default: "Courier New" on Windows, "Menlo" on Mac OS, and
|
||||
"DejaVu Sans Mono" on \\*nix
|
||||
|
||||
`font_size`
|
||||
The font size in points to be used.
|
||||
|
||||
Default: 14
|
||||
|
||||
`image_pad`
|
||||
The padding, in pixels to be used at each edge of the resulting image.
|
||||
|
||||
Default: 10
|
||||
|
||||
`line_numbers`
|
||||
Whether line numbers should be shown: True/False
|
||||
|
||||
Default: True
|
||||
|
||||
`line_number_start`
|
||||
The line number of the first line.
|
||||
|
||||
Default: 1
|
||||
|
||||
`line_number_step`
|
||||
The step used when printing line numbers.
|
||||
|
||||
Default: 1
|
||||
|
||||
`line_number_bg`
|
||||
The background colour (in "#123456" format) of the line number bar, or
|
||||
None to use the style background color.
|
||||
|
||||
Default: "#eed"
|
||||
|
||||
`line_number_fg`
|
||||
The text color of the line numbers (in "#123456"-like format).
|
||||
|
||||
Default: "#886"
|
||||
|
||||
`line_number_chars`
|
||||
The number of columns of line numbers allowable in the line number
|
||||
margin.
|
||||
|
||||
Default: 2
|
||||
|
||||
`line_number_bold`
|
||||
Whether line numbers will be bold: True/False
|
||||
|
||||
Default: False
|
||||
|
||||
`line_number_italic`
|
||||
Whether line numbers will be italicized: True/False
|
||||
|
||||
Default: False
|
||||
|
||||
`line_number_separator`
|
||||
Whether a line will be drawn between the line number area and the
|
||||
source code area: True/False
|
||||
|
||||
Default: True
|
||||
|
||||
`line_number_pad`
|
||||
The horizontal padding (in pixels) between the line number margin, and
|
||||
the source code area.
|
||||
|
||||
Default: 6
|
||||
|
||||
`hl_lines`
|
||||
Specify a list of lines to be highlighted.
|
||||
|
||||
.. versionadded:: 1.2
|
||||
|
||||
Default: empty list
|
||||
|
||||
`hl_color`
|
||||
Specify the color for highlighting lines.
|
||||
|
||||
.. versionadded:: 1.2
|
||||
|
||||
Default: highlight color of the selected style
|
||||
"""
|
||||
|
||||
# Required by the pygments mapper
|
||||
name = 'img'
|
||||
aliases = ['img', 'IMG', 'png']
|
||||
filenames = ['*.png']
|
||||
|
||||
unicodeoutput = False
|
||||
|
||||
default_image_format = 'png'
|
||||
|
||||
def __init__(self, **options):
|
||||
"""
|
||||
See the class docstring for explanation of options.
|
||||
"""
|
||||
if not pil_available:
|
||||
raise PilNotAvailable(
|
||||
'Python Imaging Library is required for this formatter')
|
||||
Formatter.__init__(self, **options)
|
||||
self.encoding = 'latin1' # let pygments.format() do the right thing
|
||||
# Read the style
|
||||
self.styles = dict(self.style)
|
||||
if self.style.background_color is None:
|
||||
self.background_color = '#fff'
|
||||
else:
|
||||
self.background_color = self.style.background_color
|
||||
# Image options
|
||||
self.image_format = get_choice_opt(
|
||||
options, 'image_format', ['png', 'jpeg', 'gif', 'bmp'],
|
||||
self.default_image_format, normcase=True)
|
||||
self.image_pad = get_int_opt(options, 'image_pad', 10)
|
||||
self.line_pad = get_int_opt(options, 'line_pad', 2)
|
||||
# The fonts
|
||||
fontsize = get_int_opt(options, 'font_size', 14)
|
||||
self.fonts = FontManager(options.get('font_name', ''), fontsize)
|
||||
self.fontw, self.fonth = self.fonts.get_char_size()
|
||||
# Line number options
|
||||
self.line_number_fg = options.get('line_number_fg', '#886')
|
||||
self.line_number_bg = options.get('line_number_bg', '#eed')
|
||||
self.line_number_chars = get_int_opt(options,
|
||||
'line_number_chars', 2)
|
||||
self.line_number_bold = get_bool_opt(options,
|
||||
'line_number_bold', False)
|
||||
self.line_number_italic = get_bool_opt(options,
|
||||
'line_number_italic', False)
|
||||
self.line_number_pad = get_int_opt(options, 'line_number_pad', 6)
|
||||
self.line_numbers = get_bool_opt(options, 'line_numbers', True)
|
||||
self.line_number_separator = get_bool_opt(options,
|
||||
'line_number_separator', True)
|
||||
self.line_number_step = get_int_opt(options, 'line_number_step', 1)
|
||||
self.line_number_start = get_int_opt(options, 'line_number_start', 1)
|
||||
if self.line_numbers:
|
||||
self.line_number_width = (self.fontw * self.line_number_chars +
|
||||
self.line_number_pad * 2)
|
||||
else:
|
||||
self.line_number_width = 0
|
||||
self.hl_lines = []
|
||||
hl_lines_str = get_list_opt(options, 'hl_lines', [])
|
||||
for line in hl_lines_str:
|
||||
try:
|
||||
self.hl_lines.append(int(line))
|
||||
except ValueError:
|
||||
pass
|
||||
self.hl_color = options.get('hl_color',
|
||||
self.style.highlight_color) or '#f90'
|
||||
self.drawables = []
|
||||
|
||||
def get_style_defs(self, arg=''):
|
||||
raise NotImplementedError('The -S option is meaningless for the image '
|
||||
'formatter. Use -O style=<stylename> instead.')
|
||||
|
||||
def _get_line_height(self):
|
||||
"""
|
||||
Get the height of a line.
|
||||
"""
|
||||
return self.fonth + self.line_pad
|
||||
|
||||
def _get_line_y(self, lineno):
|
||||
"""
|
||||
Get the Y coordinate of a line number.
|
||||
"""
|
||||
return lineno * self._get_line_height() + self.image_pad
|
||||
|
||||
def _get_char_width(self):
|
||||
"""
|
||||
Get the width of a character.
|
||||
"""
|
||||
return self.fontw
|
||||
|
||||
def _get_char_x(self, linelength):
|
||||
"""
|
||||
Get the X coordinate of a character position.
|
||||
"""
|
||||
return linelength + self.image_pad + self.line_number_width
|
||||
|
||||
def _get_text_pos(self, linelength, lineno):
|
||||
"""
|
||||
Get the actual position for a character and line position.
|
||||
"""
|
||||
return self._get_char_x(linelength), self._get_line_y(lineno)
|
||||
|
||||
def _get_linenumber_pos(self, lineno):
|
||||
"""
|
||||
Get the actual position for the start of a line number.
|
||||
"""
|
||||
return (self.image_pad, self._get_line_y(lineno))
|
||||
|
||||
def _get_text_color(self, style):
|
||||
"""
|
||||
Get the correct color for the token from the style.
|
||||
"""
|
||||
if style['color'] is not None:
|
||||
fill = '#' + style['color']
|
||||
else:
|
||||
fill = '#000'
|
||||
return fill
|
||||
|
||||
def _get_text_bg_color(self, style):
|
||||
"""
|
||||
Get the correct background color for the token from the style.
|
||||
"""
|
||||
if style['bgcolor'] is not None:
|
||||
bg_color = '#' + style['bgcolor']
|
||||
else:
|
||||
bg_color = None
|
||||
return bg_color
|
||||
|
||||
def _get_style_font(self, style):
|
||||
"""
|
||||
Get the correct font for the style.
|
||||
"""
|
||||
return self.fonts.get_font(style['bold'], style['italic'])
|
||||
|
||||
def _get_image_size(self, maxlinelength, maxlineno):
|
||||
"""
|
||||
Get the required image size.
|
||||
"""
|
||||
return (self._get_char_x(maxlinelength) + self.image_pad,
|
||||
self._get_line_y(maxlineno + 0) + self.image_pad)
|
||||
|
||||
def _draw_linenumber(self, posno, lineno):
|
||||
"""
|
||||
Remember a line number drawable to paint later.
|
||||
"""
|
||||
self._draw_text(
|
||||
self._get_linenumber_pos(posno),
|
||||
str(lineno).rjust(self.line_number_chars),
|
||||
font=self.fonts.get_font(self.line_number_bold,
|
||||
self.line_number_italic),
|
||||
text_fg=self.line_number_fg,
|
||||
text_bg=None,
|
||||
)
|
||||
|
||||
def _draw_text(self, pos, text, font, text_fg, text_bg):
|
||||
"""
|
||||
Remember a single drawable tuple to paint later.
|
||||
"""
|
||||
self.drawables.append((pos, text, font, text_fg, text_bg))
|
||||
|
||||
def _create_drawables(self, tokensource):
|
||||
"""
|
||||
Create drawables for the token content.
|
||||
"""
|
||||
lineno = charno = maxcharno = 0
|
||||
maxlinelength = linelength = 0
|
||||
for ttype, value in tokensource:
|
||||
while ttype not in self.styles:
|
||||
ttype = ttype.parent
|
||||
style = self.styles[ttype]
|
||||
# TODO: make sure tab expansion happens earlier in the chain. It
|
||||
# really ought to be done on the input, as to do it right here is
|
||||
# quite complex.
|
||||
value = value.expandtabs(4)
|
||||
lines = value.splitlines(True)
|
||||
# print lines
|
||||
for i, line in enumerate(lines):
|
||||
temp = line.rstrip('\n')
|
||||
if temp:
|
||||
self._draw_text(
|
||||
self._get_text_pos(linelength, lineno),
|
||||
temp,
|
||||
font = self._get_style_font(style),
|
||||
text_fg = self._get_text_color(style),
|
||||
text_bg = self._get_text_bg_color(style),
|
||||
)
|
||||
temp_width, _ = self.fonts.get_text_size(temp)
|
||||
linelength += temp_width
|
||||
maxlinelength = max(maxlinelength, linelength)
|
||||
charno += len(temp)
|
||||
maxcharno = max(maxcharno, charno)
|
||||
if line.endswith('\n'):
|
||||
# add a line for each extra line in the value
|
||||
linelength = 0
|
||||
charno = 0
|
||||
lineno += 1
|
||||
self.maxlinelength = maxlinelength
|
||||
self.maxcharno = maxcharno
|
||||
self.maxlineno = lineno
|
||||
|
||||
def _draw_line_numbers(self):
|
||||
"""
|
||||
Create drawables for the line numbers.
|
||||
"""
|
||||
if not self.line_numbers:
|
||||
return
|
||||
for p in range(self.maxlineno):
|
||||
n = p + self.line_number_start
|
||||
if (n % self.line_number_step) == 0:
|
||||
self._draw_linenumber(p, n)
|
||||
|
||||
def _paint_line_number_bg(self, im):
|
||||
"""
|
||||
Paint the line number background on the image.
|
||||
"""
|
||||
if not self.line_numbers:
|
||||
return
|
||||
if self.line_number_fg is None:
|
||||
return
|
||||
draw = ImageDraw.Draw(im)
|
||||
recth = im.size[-1]
|
||||
rectw = self.image_pad + self.line_number_width - self.line_number_pad
|
||||
draw.rectangle([(0, 0), (rectw, recth)],
|
||||
fill=self.line_number_bg)
|
||||
if self.line_number_separator:
|
||||
draw.line([(rectw, 0), (rectw, recth)], fill=self.line_number_fg)
|
||||
del draw
|
||||
|
||||
def format(self, tokensource, outfile):
|
||||
"""
|
||||
Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
|
||||
tuples and write it into ``outfile``.
|
||||
|
||||
This implementation calculates where it should draw each token on the
|
||||
pixmap, then calculates the required pixmap size and draws the items.
|
||||
"""
|
||||
self._create_drawables(tokensource)
|
||||
self._draw_line_numbers()
|
||||
im = Image.new(
|
||||
'RGB',
|
||||
self._get_image_size(self.maxlinelength, self.maxlineno),
|
||||
self.background_color
|
||||
)
|
||||
self._paint_line_number_bg(im)
|
||||
draw = ImageDraw.Draw(im)
|
||||
# Highlight
|
||||
if self.hl_lines:
|
||||
x = self.image_pad + self.line_number_width - self.line_number_pad + 1
|
||||
recth = self._get_line_height()
|
||||
rectw = im.size[0] - x
|
||||
for linenumber in self.hl_lines:
|
||||
y = self._get_line_y(linenumber - 1)
|
||||
draw.rectangle([(x, y), (x + rectw, y + recth)],
|
||||
fill=self.hl_color)
|
||||
for pos, value, font, text_fg, text_bg in self.drawables:
|
||||
if text_bg:
|
||||
text_size = draw.textsize(text=value, font=font)
|
||||
draw.rectangle([pos[0], pos[1], pos[0] + text_size[0], pos[1] + text_size[1]], fill=text_bg)
|
||||
draw.text(pos, value, font=font, fill=text_fg)
|
||||
im.save(outfile, self.image_format.upper())
|
||||
|
||||
|
||||
# Add one formatter per format, so that the "-f gif" option gives the correct result
|
||||
# when used in pygmentize.
|
||||
|
||||
class GifImageFormatter(ImageFormatter):
|
||||
"""
|
||||
Create a GIF image from source code. This uses the Python Imaging Library to
|
||||
generate a pixmap from the source code.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
"""
|
||||
|
||||
name = 'img_gif'
|
||||
aliases = ['gif']
|
||||
filenames = ['*.gif']
|
||||
default_image_format = 'gif'
|
||||
|
||||
|
||||
class JpgImageFormatter(ImageFormatter):
|
||||
"""
|
||||
Create a JPEG image from source code. This uses the Python Imaging Library to
|
||||
generate a pixmap from the source code.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
"""
|
||||
|
||||
name = 'img_jpg'
|
||||
aliases = ['jpg', 'jpeg']
|
||||
filenames = ['*.jpg']
|
||||
default_image_format = 'jpeg'
|
||||
|
||||
|
||||
class BmpImageFormatter(ImageFormatter):
|
||||
"""
|
||||
Create a bitmap image from source code. This uses the Python Imaging Library to
|
||||
generate a pixmap from the source code.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
"""
|
||||
|
||||
name = 'img_bmp'
|
||||
aliases = ['bmp', 'bitmap']
|
||||
filenames = ['*.bmp']
|
||||
default_image_format = 'bmp'
|
||||
@@ -0,0 +1,154 @@
|
||||
"""
|
||||
pygments.formatters.irc
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for IRC output
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
from pip._vendor.pygments.token import Keyword, Name, Comment, String, Error, \
|
||||
Number, Operator, Generic, Token, Whitespace
|
||||
from pip._vendor.pygments.util import get_choice_opt
|
||||
|
||||
|
||||
__all__ = ['IRCFormatter']
|
||||
|
||||
|
||||
#: Map token types to a tuple of color values for light and dark
|
||||
#: backgrounds.
|
||||
IRC_COLORS = {
|
||||
Token: ('', ''),
|
||||
|
||||
Whitespace: ('gray', 'brightblack'),
|
||||
Comment: ('gray', 'brightblack'),
|
||||
Comment.Preproc: ('cyan', 'brightcyan'),
|
||||
Keyword: ('blue', 'brightblue'),
|
||||
Keyword.Type: ('cyan', 'brightcyan'),
|
||||
Operator.Word: ('magenta', 'brightcyan'),
|
||||
Name.Builtin: ('cyan', 'brightcyan'),
|
||||
Name.Function: ('green', 'brightgreen'),
|
||||
Name.Namespace: ('_cyan_', '_brightcyan_'),
|
||||
Name.Class: ('_green_', '_brightgreen_'),
|
||||
Name.Exception: ('cyan', 'brightcyan'),
|
||||
Name.Decorator: ('brightblack', 'gray'),
|
||||
Name.Variable: ('red', 'brightred'),
|
||||
Name.Constant: ('red', 'brightred'),
|
||||
Name.Attribute: ('cyan', 'brightcyan'),
|
||||
Name.Tag: ('brightblue', 'brightblue'),
|
||||
String: ('yellow', 'yellow'),
|
||||
Number: ('blue', 'brightblue'),
|
||||
|
||||
Generic.Deleted: ('brightred', 'brightred'),
|
||||
Generic.Inserted: ('green', 'brightgreen'),
|
||||
Generic.Heading: ('**', '**'),
|
||||
Generic.Subheading: ('*magenta*', '*brightmagenta*'),
|
||||
Generic.Error: ('brightred', 'brightred'),
|
||||
|
||||
Error: ('_brightred_', '_brightred_'),
|
||||
}
|
||||
|
||||
|
||||
IRC_COLOR_MAP = {
|
||||
'white': 0,
|
||||
'black': 1,
|
||||
'blue': 2,
|
||||
'brightgreen': 3,
|
||||
'brightred': 4,
|
||||
'yellow': 5,
|
||||
'magenta': 6,
|
||||
'orange': 7,
|
||||
'green': 7, #compat w/ ansi
|
||||
'brightyellow': 8,
|
||||
'lightgreen': 9,
|
||||
'brightcyan': 9, # compat w/ ansi
|
||||
'cyan': 10,
|
||||
'lightblue': 11,
|
||||
'red': 11, # compat w/ ansi
|
||||
'brightblue': 12,
|
||||
'brightmagenta': 13,
|
||||
'brightblack': 14,
|
||||
'gray': 15,
|
||||
}
|
||||
|
||||
def ircformat(color, text):
|
||||
if len(color) < 1:
|
||||
return text
|
||||
add = sub = ''
|
||||
if '_' in color: # italic
|
||||
add += '\x1D'
|
||||
sub = '\x1D' + sub
|
||||
color = color.strip('_')
|
||||
if '*' in color: # bold
|
||||
add += '\x02'
|
||||
sub = '\x02' + sub
|
||||
color = color.strip('*')
|
||||
# underline (\x1F) not supported
|
||||
# backgrounds (\x03FF,BB) not supported
|
||||
if len(color) > 0: # actual color - may have issues with ircformat("red", "blah")+"10" type stuff
|
||||
add += '\x03' + str(IRC_COLOR_MAP[color]).zfill(2)
|
||||
sub = '\x03' + sub
|
||||
return add + text + sub
|
||||
return '<'+add+'>'+text+'</'+sub+'>'
|
||||
|
||||
|
||||
class IRCFormatter(Formatter):
|
||||
r"""
|
||||
Format tokens with IRC color sequences
|
||||
|
||||
The `get_style_defs()` method doesn't do anything special since there is
|
||||
no support for common styles.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`bg`
|
||||
Set to ``"light"`` or ``"dark"`` depending on the terminal's background
|
||||
(default: ``"light"``).
|
||||
|
||||
`colorscheme`
|
||||
A dictionary mapping token types to (lightbg, darkbg) color names or
|
||||
``None`` (default: ``None`` = use builtin colorscheme).
|
||||
|
||||
`linenos`
|
||||
Set to ``True`` to have line numbers in the output as well
|
||||
(default: ``False`` = no line numbers).
|
||||
"""
|
||||
name = 'IRC'
|
||||
aliases = ['irc', 'IRC']
|
||||
filenames = []
|
||||
|
||||
def __init__(self, **options):
|
||||
Formatter.__init__(self, **options)
|
||||
self.darkbg = get_choice_opt(options, 'bg',
|
||||
['light', 'dark'], 'light') == 'dark'
|
||||
self.colorscheme = options.get('colorscheme', None) or IRC_COLORS
|
||||
self.linenos = options.get('linenos', False)
|
||||
self._lineno = 0
|
||||
|
||||
def _write_lineno(self, outfile):
|
||||
if self.linenos:
|
||||
self._lineno += 1
|
||||
outfile.write("%04d: " % self._lineno)
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
self._write_lineno(outfile)
|
||||
|
||||
for ttype, value in tokensource:
|
||||
color = self.colorscheme.get(ttype)
|
||||
while color is None:
|
||||
ttype = ttype[:-1]
|
||||
color = self.colorscheme.get(ttype)
|
||||
if color:
|
||||
color = color[self.darkbg]
|
||||
spl = value.split('\n')
|
||||
for line in spl[:-1]:
|
||||
if line:
|
||||
outfile.write(ircformat(color, line))
|
||||
outfile.write('\n')
|
||||
self._write_lineno(outfile)
|
||||
if spl[-1]:
|
||||
outfile.write(ircformat(color, spl[-1]))
|
||||
else:
|
||||
outfile.write(value)
|
||||
@@ -0,0 +1,521 @@
|
||||
"""
|
||||
pygments.formatters.latex
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for LaTeX fancyvrb output.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from io import StringIO
|
||||
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
from pip._vendor.pygments.lexer import Lexer, do_insertions
|
||||
from pip._vendor.pygments.token import Token, STANDARD_TYPES
|
||||
from pip._vendor.pygments.util import get_bool_opt, get_int_opt
|
||||
|
||||
|
||||
__all__ = ['LatexFormatter']
|
||||
|
||||
|
||||
def escape_tex(text, commandprefix):
|
||||
return text.replace('\\', '\x00'). \
|
||||
replace('{', '\x01'). \
|
||||
replace('}', '\x02'). \
|
||||
replace('\x00', r'\%sZbs{}' % commandprefix). \
|
||||
replace('\x01', r'\%sZob{}' % commandprefix). \
|
||||
replace('\x02', r'\%sZcb{}' % commandprefix). \
|
||||
replace('^', r'\%sZca{}' % commandprefix). \
|
||||
replace('_', r'\%sZus{}' % commandprefix). \
|
||||
replace('&', r'\%sZam{}' % commandprefix). \
|
||||
replace('<', r'\%sZlt{}' % commandprefix). \
|
||||
replace('>', r'\%sZgt{}' % commandprefix). \
|
||||
replace('#', r'\%sZsh{}' % commandprefix). \
|
||||
replace('%', r'\%sZpc{}' % commandprefix). \
|
||||
replace('$', r'\%sZdl{}' % commandprefix). \
|
||||
replace('-', r'\%sZhy{}' % commandprefix). \
|
||||
replace("'", r'\%sZsq{}' % commandprefix). \
|
||||
replace('"', r'\%sZdq{}' % commandprefix). \
|
||||
replace('~', r'\%sZti{}' % commandprefix)
|
||||
|
||||
|
||||
DOC_TEMPLATE = r'''
|
||||
\documentclass{%(docclass)s}
|
||||
\usepackage{fancyvrb}
|
||||
\usepackage{color}
|
||||
\usepackage[%(encoding)s]{inputenc}
|
||||
%(preamble)s
|
||||
|
||||
%(styledefs)s
|
||||
|
||||
\begin{document}
|
||||
|
||||
\section*{%(title)s}
|
||||
|
||||
%(code)s
|
||||
\end{document}
|
||||
'''
|
||||
|
||||
## Small explanation of the mess below :)
|
||||
#
|
||||
# The previous version of the LaTeX formatter just assigned a command to
|
||||
# each token type defined in the current style. That obviously is
|
||||
# problematic if the highlighted code is produced for a different style
|
||||
# than the style commands themselves.
|
||||
#
|
||||
# This version works much like the HTML formatter which assigns multiple
|
||||
# CSS classes to each <span> tag, from the most specific to the least
|
||||
# specific token type, thus falling back to the parent token type if one
|
||||
# is not defined. Here, the classes are there too and use the same short
|
||||
# forms given in token.STANDARD_TYPES.
|
||||
#
|
||||
# Highlighted code now only uses one custom command, which by default is
|
||||
# \PY and selectable by the commandprefix option (and in addition the
|
||||
# escapes \PYZat, \PYZlb and \PYZrb which haven't been renamed for
|
||||
# backwards compatibility purposes).
|
||||
#
|
||||
# \PY has two arguments: the classes, separated by +, and the text to
|
||||
# render in that style. The classes are resolved into the respective
|
||||
# style commands by magic, which serves to ignore unknown classes.
|
||||
#
|
||||
# The magic macros are:
|
||||
# * \PY@it, \PY@bf, etc. are unconditionally wrapped around the text
|
||||
# to render in \PY@do. Their definition determines the style.
|
||||
# * \PY@reset resets \PY@it etc. to do nothing.
|
||||
# * \PY@toks parses the list of classes, using magic inspired by the
|
||||
# keyval package (but modified to use plusses instead of commas
|
||||
# because fancyvrb redefines commas inside its environments).
|
||||
# * \PY@tok processes one class, calling the \PY@tok@classname command
|
||||
# if it exists.
|
||||
# * \PY@tok@classname sets the \PY@it etc. to reflect the chosen style
|
||||
# for its class.
|
||||
# * \PY resets the style, parses the classnames and then calls \PY@do.
|
||||
#
|
||||
# Tip: to read this code, print it out in substituted form using e.g.
|
||||
# >>> print STYLE_TEMPLATE % {'cp': 'PY'}
|
||||
|
||||
STYLE_TEMPLATE = r'''
|
||||
\makeatletter
|
||||
\def\%(cp)s@reset{\let\%(cp)s@it=\relax \let\%(cp)s@bf=\relax%%
|
||||
\let\%(cp)s@ul=\relax \let\%(cp)s@tc=\relax%%
|
||||
\let\%(cp)s@bc=\relax \let\%(cp)s@ff=\relax}
|
||||
\def\%(cp)s@tok#1{\csname %(cp)s@tok@#1\endcsname}
|
||||
\def\%(cp)s@toks#1+{\ifx\relax#1\empty\else%%
|
||||
\%(cp)s@tok{#1}\expandafter\%(cp)s@toks\fi}
|
||||
\def\%(cp)s@do#1{\%(cp)s@bc{\%(cp)s@tc{\%(cp)s@ul{%%
|
||||
\%(cp)s@it{\%(cp)s@bf{\%(cp)s@ff{#1}}}}}}}
|
||||
\def\%(cp)s#1#2{\%(cp)s@reset\%(cp)s@toks#1+\relax+\%(cp)s@do{#2}}
|
||||
|
||||
%(styles)s
|
||||
|
||||
\def\%(cp)sZbs{\char`\\}
|
||||
\def\%(cp)sZus{\char`\_}
|
||||
\def\%(cp)sZob{\char`\{}
|
||||
\def\%(cp)sZcb{\char`\}}
|
||||
\def\%(cp)sZca{\char`\^}
|
||||
\def\%(cp)sZam{\char`\&}
|
||||
\def\%(cp)sZlt{\char`\<}
|
||||
\def\%(cp)sZgt{\char`\>}
|
||||
\def\%(cp)sZsh{\char`\#}
|
||||
\def\%(cp)sZpc{\char`\%%}
|
||||
\def\%(cp)sZdl{\char`\$}
|
||||
\def\%(cp)sZhy{\char`\-}
|
||||
\def\%(cp)sZsq{\char`\'}
|
||||
\def\%(cp)sZdq{\char`\"}
|
||||
\def\%(cp)sZti{\char`\~}
|
||||
%% for compatibility with earlier versions
|
||||
\def\%(cp)sZat{@}
|
||||
\def\%(cp)sZlb{[}
|
||||
\def\%(cp)sZrb{]}
|
||||
\makeatother
|
||||
'''
|
||||
|
||||
|
||||
def _get_ttype_name(ttype):
|
||||
fname = STANDARD_TYPES.get(ttype)
|
||||
if fname:
|
||||
return fname
|
||||
aname = ''
|
||||
while fname is None:
|
||||
aname = ttype[-1] + aname
|
||||
ttype = ttype.parent
|
||||
fname = STANDARD_TYPES.get(ttype)
|
||||
return fname + aname
|
||||
|
||||
|
||||
class LatexFormatter(Formatter):
|
||||
r"""
|
||||
Format tokens as LaTeX code. This needs the `fancyvrb` and `color`
|
||||
standard packages.
|
||||
|
||||
Without the `full` option, code is formatted as one ``Verbatim``
|
||||
environment, like this:
|
||||
|
||||
.. sourcecode:: latex
|
||||
|
||||
\begin{Verbatim}[commandchars=\\\{\}]
|
||||
\PY{k}{def }\PY{n+nf}{foo}(\PY{n}{bar}):
|
||||
\PY{k}{pass}
|
||||
\end{Verbatim}
|
||||
|
||||
Wrapping can be disabled using the `nowrap` option.
|
||||
|
||||
The special command used here (``\PY``) and all the other macros it needs
|
||||
are output by the `get_style_defs` method.
|
||||
|
||||
With the `full` option, a complete LaTeX document is output, including
|
||||
the command definitions in the preamble.
|
||||
|
||||
The `get_style_defs()` method of a `LatexFormatter` returns a string
|
||||
containing ``\def`` commands defining the macros needed inside the
|
||||
``Verbatim`` environments.
|
||||
|
||||
Additional options accepted:
|
||||
|
||||
`nowrap`
|
||||
If set to ``True``, don't wrap the tokens at all, not even inside a
|
||||
``\begin{Verbatim}`` environment. This disables most other options
|
||||
(default: ``False``).
|
||||
|
||||
`style`
|
||||
The style to use, can be a string or a Style subclass (default:
|
||||
``'default'``).
|
||||
|
||||
`full`
|
||||
Tells the formatter to output a "full" document, i.e. a complete
|
||||
self-contained document (default: ``False``).
|
||||
|
||||
`title`
|
||||
If `full` is true, the title that should be used to caption the
|
||||
document (default: ``''``).
|
||||
|
||||
`docclass`
|
||||
If the `full` option is enabled, this is the document class to use
|
||||
(default: ``'article'``).
|
||||
|
||||
`preamble`
|
||||
If the `full` option is enabled, this can be further preamble commands,
|
||||
e.g. ``\usepackage`` (default: ``''``).
|
||||
|
||||
`linenos`
|
||||
If set to ``True``, output line numbers (default: ``False``).
|
||||
|
||||
`linenostart`
|
||||
The line number for the first line (default: ``1``).
|
||||
|
||||
`linenostep`
|
||||
If set to a number n > 1, only every nth line number is printed.
|
||||
|
||||
`verboptions`
|
||||
Additional options given to the Verbatim environment (see the *fancyvrb*
|
||||
docs for possible values) (default: ``''``).
|
||||
|
||||
`commandprefix`
|
||||
The LaTeX commands used to produce colored output are constructed
|
||||
using this prefix and some letters (default: ``'PY'``).
|
||||
|
||||
.. versionadded:: 0.7
|
||||
.. versionchanged:: 0.10
|
||||
The default is now ``'PY'`` instead of ``'C'``.
|
||||
|
||||
`texcomments`
|
||||
If set to ``True``, enables LaTeX comment lines. That is, LaTex markup
|
||||
in comment tokens is not escaped so that LaTeX can render it (default:
|
||||
``False``).
|
||||
|
||||
.. versionadded:: 1.2
|
||||
|
||||
`mathescape`
|
||||
If set to ``True``, enables LaTeX math mode escape in comments. That
|
||||
is, ``'$...$'`` inside a comment will trigger math mode (default:
|
||||
``False``).
|
||||
|
||||
.. versionadded:: 1.2
|
||||
|
||||
`escapeinside`
|
||||
If set to a string of length 2, enables escaping to LaTeX. Text
|
||||
delimited by these 2 characters is read as LaTeX code and
|
||||
typeset accordingly. It has no effect in string literals. It has
|
||||
no effect in comments if `texcomments` or `mathescape` is
|
||||
set. (default: ``''``).
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
`envname`
|
||||
Allows you to pick an alternative environment name replacing Verbatim.
|
||||
The alternate environment still has to support Verbatim's option syntax.
|
||||
(default: ``'Verbatim'``).
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
name = 'LaTeX'
|
||||
aliases = ['latex', 'tex']
|
||||
filenames = ['*.tex']
|
||||
|
||||
def __init__(self, **options):
|
||||
Formatter.__init__(self, **options)
|
||||
self.nowrap = get_bool_opt(options, 'nowrap', False)
|
||||
self.docclass = options.get('docclass', 'article')
|
||||
self.preamble = options.get('preamble', '')
|
||||
self.linenos = get_bool_opt(options, 'linenos', False)
|
||||
self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
|
||||
self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
|
||||
self.verboptions = options.get('verboptions', '')
|
||||
self.nobackground = get_bool_opt(options, 'nobackground', False)
|
||||
self.commandprefix = options.get('commandprefix', 'PY')
|
||||
self.texcomments = get_bool_opt(options, 'texcomments', False)
|
||||
self.mathescape = get_bool_opt(options, 'mathescape', False)
|
||||
self.escapeinside = options.get('escapeinside', '')
|
||||
if len(self.escapeinside) == 2:
|
||||
self.left = self.escapeinside[0]
|
||||
self.right = self.escapeinside[1]
|
||||
else:
|
||||
self.escapeinside = ''
|
||||
self.envname = options.get('envname', 'Verbatim')
|
||||
|
||||
self._create_stylesheet()
|
||||
|
||||
def _create_stylesheet(self):
|
||||
t2n = self.ttype2name = {Token: ''}
|
||||
c2d = self.cmd2def = {}
|
||||
cp = self.commandprefix
|
||||
|
||||
def rgbcolor(col):
|
||||
if col:
|
||||
return ','.join(['%.2f' % (int(col[i] + col[i + 1], 16) / 255.0)
|
||||
for i in (0, 2, 4)])
|
||||
else:
|
||||
return '1,1,1'
|
||||
|
||||
for ttype, ndef in self.style:
|
||||
name = _get_ttype_name(ttype)
|
||||
cmndef = ''
|
||||
if ndef['bold']:
|
||||
cmndef += r'\let\$$@bf=\textbf'
|
||||
if ndef['italic']:
|
||||
cmndef += r'\let\$$@it=\textit'
|
||||
if ndef['underline']:
|
||||
cmndef += r'\let\$$@ul=\underline'
|
||||
if ndef['roman']:
|
||||
cmndef += r'\let\$$@ff=\textrm'
|
||||
if ndef['sans']:
|
||||
cmndef += r'\let\$$@ff=\textsf'
|
||||
if ndef['mono']:
|
||||
cmndef += r'\let\$$@ff=\textsf'
|
||||
if ndef['color']:
|
||||
cmndef += (r'\def\$$@tc##1{\textcolor[rgb]{%s}{##1}}' %
|
||||
rgbcolor(ndef['color']))
|
||||
if ndef['border']:
|
||||
cmndef += (r'\def\$$@bc##1{{\setlength{\fboxsep}{\string -\fboxrule}'
|
||||
r'\fcolorbox[rgb]{%s}{%s}{\strut ##1}}}' %
|
||||
(rgbcolor(ndef['border']),
|
||||
rgbcolor(ndef['bgcolor'])))
|
||||
elif ndef['bgcolor']:
|
||||
cmndef += (r'\def\$$@bc##1{{\setlength{\fboxsep}{0pt}'
|
||||
r'\colorbox[rgb]{%s}{\strut ##1}}}' %
|
||||
rgbcolor(ndef['bgcolor']))
|
||||
if cmndef == '':
|
||||
continue
|
||||
cmndef = cmndef.replace('$$', cp)
|
||||
t2n[ttype] = name
|
||||
c2d[name] = cmndef
|
||||
|
||||
def get_style_defs(self, arg=''):
|
||||
"""
|
||||
Return the command sequences needed to define the commands
|
||||
used to format text in the verbatim environment. ``arg`` is ignored.
|
||||
"""
|
||||
cp = self.commandprefix
|
||||
styles = []
|
||||
for name, definition in self.cmd2def.items():
|
||||
styles.append(r'\@namedef{%s@tok@%s}{%s}' % (cp, name, definition))
|
||||
return STYLE_TEMPLATE % {'cp': self.commandprefix,
|
||||
'styles': '\n'.join(styles)}
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
# TODO: add support for background colors
|
||||
t2n = self.ttype2name
|
||||
cp = self.commandprefix
|
||||
|
||||
if self.full:
|
||||
realoutfile = outfile
|
||||
outfile = StringIO()
|
||||
|
||||
if not self.nowrap:
|
||||
outfile.write('\\begin{' + self.envname + '}[commandchars=\\\\\\{\\}')
|
||||
if self.linenos:
|
||||
start, step = self.linenostart, self.linenostep
|
||||
outfile.write(',numbers=left' +
|
||||
(start and ',firstnumber=%d' % start or '') +
|
||||
(step and ',stepnumber=%d' % step or ''))
|
||||
if self.mathescape or self.texcomments or self.escapeinside:
|
||||
outfile.write(',codes={\\catcode`\\$=3\\catcode`\\^=7'
|
||||
'\\catcode`\\_=8\\relax}')
|
||||
if self.verboptions:
|
||||
outfile.write(',' + self.verboptions)
|
||||
outfile.write(']\n')
|
||||
|
||||
for ttype, value in tokensource:
|
||||
if ttype in Token.Comment:
|
||||
if self.texcomments:
|
||||
# Try to guess comment starting lexeme and escape it ...
|
||||
start = value[0:1]
|
||||
for i in range(1, len(value)):
|
||||
if start[0] != value[i]:
|
||||
break
|
||||
start += value[i]
|
||||
|
||||
value = value[len(start):]
|
||||
start = escape_tex(start, cp)
|
||||
|
||||
# ... but do not escape inside comment.
|
||||
value = start + value
|
||||
elif self.mathescape:
|
||||
# Only escape parts not inside a math environment.
|
||||
parts = value.split('$')
|
||||
in_math = False
|
||||
for i, part in enumerate(parts):
|
||||
if not in_math:
|
||||
parts[i] = escape_tex(part, cp)
|
||||
in_math = not in_math
|
||||
value = '$'.join(parts)
|
||||
elif self.escapeinside:
|
||||
text = value
|
||||
value = ''
|
||||
while text:
|
||||
a, sep1, text = text.partition(self.left)
|
||||
if sep1:
|
||||
b, sep2, text = text.partition(self.right)
|
||||
if sep2:
|
||||
value += escape_tex(a, cp) + b
|
||||
else:
|
||||
value += escape_tex(a + sep1 + b, cp)
|
||||
else:
|
||||
value += escape_tex(a, cp)
|
||||
else:
|
||||
value = escape_tex(value, cp)
|
||||
elif ttype not in Token.Escape:
|
||||
value = escape_tex(value, cp)
|
||||
styles = []
|
||||
while ttype is not Token:
|
||||
try:
|
||||
styles.append(t2n[ttype])
|
||||
except KeyError:
|
||||
# not in current style
|
||||
styles.append(_get_ttype_name(ttype))
|
||||
ttype = ttype.parent
|
||||
styleval = '+'.join(reversed(styles))
|
||||
if styleval:
|
||||
spl = value.split('\n')
|
||||
for line in spl[:-1]:
|
||||
if line:
|
||||
outfile.write("\\%s{%s}{%s}" % (cp, styleval, line))
|
||||
outfile.write('\n')
|
||||
if spl[-1]:
|
||||
outfile.write("\\%s{%s}{%s}" % (cp, styleval, spl[-1]))
|
||||
else:
|
||||
outfile.write(value)
|
||||
|
||||
if not self.nowrap:
|
||||
outfile.write('\\end{' + self.envname + '}\n')
|
||||
|
||||
if self.full:
|
||||
encoding = self.encoding or 'utf8'
|
||||
# map known existings encodings from LaTeX distribution
|
||||
encoding = {
|
||||
'utf_8': 'utf8',
|
||||
'latin_1': 'latin1',
|
||||
'iso_8859_1': 'latin1',
|
||||
}.get(encoding.replace('-', '_'), encoding)
|
||||
realoutfile.write(DOC_TEMPLATE %
|
||||
dict(docclass = self.docclass,
|
||||
preamble = self.preamble,
|
||||
title = self.title,
|
||||
encoding = encoding,
|
||||
styledefs = self.get_style_defs(),
|
||||
code = outfile.getvalue()))
|
||||
|
||||
|
||||
class LatexEmbeddedLexer(Lexer):
|
||||
"""
|
||||
This lexer takes one lexer as argument, the lexer for the language
|
||||
being formatted, and the left and right delimiters for escaped text.
|
||||
|
||||
First everything is scanned using the language lexer to obtain
|
||||
strings and comments. All other consecutive tokens are merged and
|
||||
the resulting text is scanned for escaped segments, which are given
|
||||
the Token.Escape type. Finally text that is not escaped is scanned
|
||||
again with the language lexer.
|
||||
"""
|
||||
def __init__(self, left, right, lang, **options):
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.lang = lang
|
||||
Lexer.__init__(self, **options)
|
||||
|
||||
def get_tokens_unprocessed(self, text):
|
||||
# find and remove all the escape tokens (replace with an empty string)
|
||||
# this is very similar to DelegatingLexer.get_tokens_unprocessed.
|
||||
buffered = ''
|
||||
insertions = []
|
||||
insertion_buf = []
|
||||
for i, t, v in self._find_safe_escape_tokens(text):
|
||||
if t is None:
|
||||
if insertion_buf:
|
||||
insertions.append((len(buffered), insertion_buf))
|
||||
insertion_buf = []
|
||||
buffered += v
|
||||
else:
|
||||
insertion_buf.append((i, t, v))
|
||||
if insertion_buf:
|
||||
insertions.append((len(buffered), insertion_buf))
|
||||
return do_insertions(insertions,
|
||||
self.lang.get_tokens_unprocessed(buffered))
|
||||
|
||||
def _find_safe_escape_tokens(self, text):
|
||||
""" find escape tokens that are not in strings or comments """
|
||||
for i, t, v in self._filter_to(
|
||||
self.lang.get_tokens_unprocessed(text),
|
||||
lambda t: t in Token.Comment or t in Token.String
|
||||
):
|
||||
if t is None:
|
||||
for i2, t2, v2 in self._find_escape_tokens(v):
|
||||
yield i + i2, t2, v2
|
||||
else:
|
||||
yield i, None, v
|
||||
|
||||
def _filter_to(self, it, pred):
|
||||
""" Keep only the tokens that match `pred`, merge the others together """
|
||||
buf = ''
|
||||
idx = 0
|
||||
for i, t, v in it:
|
||||
if pred(t):
|
||||
if buf:
|
||||
yield idx, None, buf
|
||||
buf = ''
|
||||
yield i, t, v
|
||||
else:
|
||||
if not buf:
|
||||
idx = i
|
||||
buf += v
|
||||
if buf:
|
||||
yield idx, None, buf
|
||||
|
||||
def _find_escape_tokens(self, text):
|
||||
""" Find escape tokens within text, give token=None otherwise """
|
||||
index = 0
|
||||
while text:
|
||||
a, sep1, text = text.partition(self.left)
|
||||
if a:
|
||||
yield index, None, a
|
||||
index += len(a)
|
||||
if sep1:
|
||||
b, sep2, text = text.partition(self.right)
|
||||
if sep2:
|
||||
yield index + len(sep1), Token.Escape, b
|
||||
index += len(sep1) + len(b) + len(sep2)
|
||||
else:
|
||||
yield index, Token.Error, sep1
|
||||
index += len(sep1)
|
||||
text = b
|
||||
@@ -0,0 +1,161 @@
|
||||
"""
|
||||
pygments.formatters.other
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Other formatters: NullFormatter, RawTokenFormatter.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
from pip._vendor.pygments.util import get_choice_opt
|
||||
from pip._vendor.pygments.token import Token
|
||||
from pip._vendor.pygments.console import colorize
|
||||
|
||||
__all__ = ['NullFormatter', 'RawTokenFormatter', 'TestcaseFormatter']
|
||||
|
||||
|
||||
class NullFormatter(Formatter):
|
||||
"""
|
||||
Output the text unchanged without any formatting.
|
||||
"""
|
||||
name = 'Text only'
|
||||
aliases = ['text', 'null']
|
||||
filenames = ['*.txt']
|
||||
|
||||
def format(self, tokensource, outfile):
|
||||
enc = self.encoding
|
||||
for ttype, value in tokensource:
|
||||
if enc:
|
||||
outfile.write(value.encode(enc))
|
||||
else:
|
||||
outfile.write(value)
|
||||
|
||||
|
||||
class RawTokenFormatter(Formatter):
|
||||
r"""
|
||||
Format tokens as a raw representation for storing token streams.
|
||||
|
||||
The format is ``tokentype<TAB>repr(tokenstring)\n``. The output can later
|
||||
be converted to a token stream with the `RawTokenLexer`, described in the
|
||||
:doc:`lexer list <lexers>`.
|
||||
|
||||
Only two options are accepted:
|
||||
|
||||
`compress`
|
||||
If set to ``'gz'`` or ``'bz2'``, compress the output with the given
|
||||
compression algorithm after encoding (default: ``''``).
|
||||
`error_color`
|
||||
If set to a color name, highlight error tokens using that color. If
|
||||
set but with no value, defaults to ``'red'``.
|
||||
|
||||
.. versionadded:: 0.11
|
||||
|
||||
"""
|
||||
name = 'Raw tokens'
|
||||
aliases = ['raw', 'tokens']
|
||||
filenames = ['*.raw']
|
||||
|
||||
unicodeoutput = False
|
||||
|
||||
def __init__(self, **options):
|
||||
Formatter.__init__(self, **options)
|
||||
# We ignore self.encoding if it is set, since it gets set for lexer
|
||||
# and formatter if given with -Oencoding on the command line.
|
||||
# The RawTokenFormatter outputs only ASCII. Override here.
|
||||
self.encoding = 'ascii' # let pygments.format() do the right thing
|
||||
self.compress = get_choice_opt(options, 'compress',
|
||||
['', 'none', 'gz', 'bz2'], '')
|
||||
self.error_color = options.get('error_color', None)
|
||||
if self.error_color is True:
|
||||
self.error_color = 'red'
|
||||
if self.error_color is not None:
|
||||
try:
|
||||
colorize(self.error_color, '')
|
||||
except KeyError:
|
||||
raise ValueError("Invalid color %r specified" %
|
||||
self.error_color)
|
||||
|
||||
def format(self, tokensource, outfile):
|
||||
try:
|
||||
outfile.write(b'')
|
||||
except TypeError:
|
||||
raise TypeError('The raw tokens formatter needs a binary '
|
||||
'output file')
|
||||
if self.compress == 'gz':
|
||||
import gzip
|
||||
outfile = gzip.GzipFile('', 'wb', 9, outfile)
|
||||
|
||||
write = outfile.write
|
||||
flush = outfile.close
|
||||
elif self.compress == 'bz2':
|
||||
import bz2
|
||||
compressor = bz2.BZ2Compressor(9)
|
||||
|
||||
def write(text):
|
||||
outfile.write(compressor.compress(text))
|
||||
|
||||
def flush():
|
||||
outfile.write(compressor.flush())
|
||||
outfile.flush()
|
||||
else:
|
||||
write = outfile.write
|
||||
flush = outfile.flush
|
||||
|
||||
if self.error_color:
|
||||
for ttype, value in tokensource:
|
||||
line = b"%r\t%r\n" % (ttype, value)
|
||||
if ttype is Token.Error:
|
||||
write(colorize(self.error_color, line))
|
||||
else:
|
||||
write(line)
|
||||
else:
|
||||
for ttype, value in tokensource:
|
||||
write(b"%r\t%r\n" % (ttype, value))
|
||||
flush()
|
||||
|
||||
|
||||
TESTCASE_BEFORE = '''\
|
||||
def testNeedsName(lexer):
|
||||
fragment = %r
|
||||
tokens = [
|
||||
'''
|
||||
TESTCASE_AFTER = '''\
|
||||
]
|
||||
assert list(lexer.get_tokens(fragment)) == tokens
|
||||
'''
|
||||
|
||||
|
||||
class TestcaseFormatter(Formatter):
|
||||
"""
|
||||
Format tokens as appropriate for a new testcase.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
name = 'Testcase'
|
||||
aliases = ['testcase']
|
||||
|
||||
def __init__(self, **options):
|
||||
Formatter.__init__(self, **options)
|
||||
if self.encoding is not None and self.encoding != 'utf-8':
|
||||
raise ValueError("Only None and utf-8 are allowed encodings.")
|
||||
|
||||
def format(self, tokensource, outfile):
|
||||
indentation = ' ' * 12
|
||||
rawbuf = []
|
||||
outbuf = []
|
||||
for ttype, value in tokensource:
|
||||
rawbuf.append(value)
|
||||
outbuf.append('%s(%s, %r),\n' % (indentation, ttype, value))
|
||||
|
||||
before = TESTCASE_BEFORE % (''.join(rawbuf),)
|
||||
during = ''.join(outbuf)
|
||||
after = TESTCASE_AFTER
|
||||
if self.encoding is None:
|
||||
outfile.write(before + during + after)
|
||||
else:
|
||||
outfile.write(before.encode('utf-8'))
|
||||
outfile.write(during.encode('utf-8'))
|
||||
outfile.write(after.encode('utf-8'))
|
||||
outfile.flush()
|
||||
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
pygments.formatters.pangomarkup
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for Pango markup output.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
|
||||
|
||||
__all__ = ['PangoMarkupFormatter']
|
||||
|
||||
|
||||
_escape_table = {
|
||||
ord('&'): '&',
|
||||
ord('<'): '<',
|
||||
}
|
||||
|
||||
|
||||
def escape_special_chars(text, table=_escape_table):
|
||||
"""Escape & and < for Pango Markup."""
|
||||
return text.translate(table)
|
||||
|
||||
|
||||
class PangoMarkupFormatter(Formatter):
|
||||
"""
|
||||
Format tokens as Pango Markup code. It can then be rendered to an SVG.
|
||||
|
||||
.. versionadded:: 2.9
|
||||
"""
|
||||
|
||||
name = 'Pango Markup'
|
||||
aliases = ['pango', 'pangomarkup']
|
||||
filenames = []
|
||||
|
||||
def __init__(self, **options):
|
||||
Formatter.__init__(self, **options)
|
||||
|
||||
self.styles = {}
|
||||
|
||||
for token, style in self.style:
|
||||
start = ''
|
||||
end = ''
|
||||
if style['color']:
|
||||
start += '<span fgcolor="#%s">' % style['color']
|
||||
end = '</span>' + end
|
||||
if style['bold']:
|
||||
start += '<b>'
|
||||
end = '</b>' + end
|
||||
if style['italic']:
|
||||
start += '<i>'
|
||||
end = '</i>' + end
|
||||
if style['underline']:
|
||||
start += '<u>'
|
||||
end = '</u>' + end
|
||||
self.styles[token] = (start, end)
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
lastval = ''
|
||||
lasttype = None
|
||||
|
||||
outfile.write('<tt>')
|
||||
|
||||
for ttype, value in tokensource:
|
||||
while ttype not in self.styles:
|
||||
ttype = ttype.parent
|
||||
if ttype == lasttype:
|
||||
lastval += escape_special_chars(value)
|
||||
else:
|
||||
if lastval:
|
||||
stylebegin, styleend = self.styles[lasttype]
|
||||
outfile.write(stylebegin + lastval + styleend)
|
||||
lastval = escape_special_chars(value)
|
||||
lasttype = ttype
|
||||
|
||||
if lastval:
|
||||
stylebegin, styleend = self.styles[lasttype]
|
||||
outfile.write(stylebegin + lastval + styleend)
|
||||
|
||||
outfile.write('</tt>')
|
||||
@@ -0,0 +1,146 @@
|
||||
"""
|
||||
pygments.formatters.rtf
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A formatter that generates RTF files.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
from pip._vendor.pygments.util import get_int_opt, surrogatepair
|
||||
|
||||
|
||||
__all__ = ['RtfFormatter']
|
||||
|
||||
|
||||
class RtfFormatter(Formatter):
|
||||
"""
|
||||
Format tokens as RTF markup. This formatter automatically outputs full RTF
|
||||
documents with color information and other useful stuff. Perfect for Copy and
|
||||
Paste into Microsoft(R) Word(R) documents.
|
||||
|
||||
Please note that ``encoding`` and ``outencoding`` options are ignored.
|
||||
The RTF format is ASCII natively, but handles unicode characters correctly
|
||||
thanks to escape sequences.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
Additional options accepted:
|
||||
|
||||
`style`
|
||||
The style to use, can be a string or a Style subclass (default:
|
||||
``'default'``).
|
||||
|
||||
`fontface`
|
||||
The used font family, for example ``Bitstream Vera Sans``. Defaults to
|
||||
some generic font which is supposed to have fixed width.
|
||||
|
||||
`fontsize`
|
||||
Size of the font used. Size is specified in half points. The
|
||||
default is 24 half-points, giving a size 12 font.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
name = 'RTF'
|
||||
aliases = ['rtf']
|
||||
filenames = ['*.rtf']
|
||||
|
||||
def __init__(self, **options):
|
||||
r"""
|
||||
Additional options accepted:
|
||||
|
||||
``fontface``
|
||||
Name of the font used. Could for example be ``'Courier New'``
|
||||
to further specify the default which is ``'\fmodern'``. The RTF
|
||||
specification claims that ``\fmodern`` are "Fixed-pitch serif
|
||||
and sans serif fonts". Hope every RTF implementation thinks
|
||||
the same about modern...
|
||||
|
||||
"""
|
||||
Formatter.__init__(self, **options)
|
||||
self.fontface = options.get('fontface') or ''
|
||||
self.fontsize = get_int_opt(options, 'fontsize', 0)
|
||||
|
||||
def _escape(self, text):
|
||||
return text.replace('\\', '\\\\') \
|
||||
.replace('{', '\\{') \
|
||||
.replace('}', '\\}')
|
||||
|
||||
def _escape_text(self, text):
|
||||
# empty strings, should give a small performance improvement
|
||||
if not text:
|
||||
return ''
|
||||
|
||||
# escape text
|
||||
text = self._escape(text)
|
||||
|
||||
buf = []
|
||||
for c in text:
|
||||
cn = ord(c)
|
||||
if cn < (2**7):
|
||||
# ASCII character
|
||||
buf.append(str(c))
|
||||
elif (2**7) <= cn < (2**16):
|
||||
# single unicode escape sequence
|
||||
buf.append('{\\u%d}' % cn)
|
||||
elif (2**16) <= cn:
|
||||
# RTF limits unicode to 16 bits.
|
||||
# Force surrogate pairs
|
||||
buf.append('{\\u%d}{\\u%d}' % surrogatepair(cn))
|
||||
|
||||
return ''.join(buf).replace('\n', '\\par\n')
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
# rtf 1.8 header
|
||||
outfile.write('{\\rtf1\\ansi\\uc0\\deff0'
|
||||
'{\\fonttbl{\\f0\\fmodern\\fprq1\\fcharset0%s;}}'
|
||||
'{\\colortbl;' % (self.fontface and
|
||||
' ' + self._escape(self.fontface) or
|
||||
''))
|
||||
|
||||
# convert colors and save them in a mapping to access them later.
|
||||
color_mapping = {}
|
||||
offset = 1
|
||||
for _, style in self.style:
|
||||
for color in style['color'], style['bgcolor'], style['border']:
|
||||
if color and color not in color_mapping:
|
||||
color_mapping[color] = offset
|
||||
outfile.write('\\red%d\\green%d\\blue%d;' % (
|
||||
int(color[0:2], 16),
|
||||
int(color[2:4], 16),
|
||||
int(color[4:6], 16)
|
||||
))
|
||||
offset += 1
|
||||
outfile.write('}\\f0 ')
|
||||
if self.fontsize:
|
||||
outfile.write('\\fs%d' % self.fontsize)
|
||||
|
||||
# highlight stream
|
||||
for ttype, value in tokensource:
|
||||
while not self.style.styles_token(ttype) and ttype.parent:
|
||||
ttype = ttype.parent
|
||||
style = self.style.style_for_token(ttype)
|
||||
buf = []
|
||||
if style['bgcolor']:
|
||||
buf.append('\\cb%d' % color_mapping[style['bgcolor']])
|
||||
if style['color']:
|
||||
buf.append('\\cf%d' % color_mapping[style['color']])
|
||||
if style['bold']:
|
||||
buf.append('\\b')
|
||||
if style['italic']:
|
||||
buf.append('\\i')
|
||||
if style['underline']:
|
||||
buf.append('\\ul')
|
||||
if style['border']:
|
||||
buf.append('\\chbrdr\\chcfpat%d' %
|
||||
color_mapping[style['border']])
|
||||
start = ''.join(buf)
|
||||
if start:
|
||||
outfile.write('{%s ' % start)
|
||||
outfile.write(self._escape_text(value))
|
||||
if start:
|
||||
outfile.write('}')
|
||||
|
||||
outfile.write('}')
|
||||
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
pygments.formatters.svg
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for SVG output.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
from pip._vendor.pygments.token import Comment
|
||||
from pip._vendor.pygments.util import get_bool_opt, get_int_opt
|
||||
|
||||
__all__ = ['SvgFormatter']
|
||||
|
||||
|
||||
def escape_html(text):
|
||||
"""Escape &, <, > as well as single and double quotes for HTML."""
|
||||
return text.replace('&', '&'). \
|
||||
replace('<', '<'). \
|
||||
replace('>', '>'). \
|
||||
replace('"', '"'). \
|
||||
replace("'", ''')
|
||||
|
||||
|
||||
class2style = {}
|
||||
|
||||
class SvgFormatter(Formatter):
|
||||
"""
|
||||
Format tokens as an SVG graphics file. This formatter is still experimental.
|
||||
Each line of code is a ``<text>`` element with explicit ``x`` and ``y``
|
||||
coordinates containing ``<tspan>`` elements with the individual token styles.
|
||||
|
||||
By default, this formatter outputs a full SVG document including doctype
|
||||
declaration and the ``<svg>`` root element.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
|
||||
Additional options accepted:
|
||||
|
||||
`nowrap`
|
||||
Don't wrap the SVG ``<text>`` elements in ``<svg><g>`` elements and
|
||||
don't add a XML declaration and a doctype. If true, the `fontfamily`
|
||||
and `fontsize` options are ignored. Defaults to ``False``.
|
||||
|
||||
`fontfamily`
|
||||
The value to give the wrapping ``<g>`` element's ``font-family``
|
||||
attribute, defaults to ``"monospace"``.
|
||||
|
||||
`fontsize`
|
||||
The value to give the wrapping ``<g>`` element's ``font-size``
|
||||
attribute, defaults to ``"14px"``.
|
||||
|
||||
`linenos`
|
||||
If ``True``, add line numbers (default: ``False``).
|
||||
|
||||
`linenostart`
|
||||
The line number for the first line (default: ``1``).
|
||||
|
||||
`linenostep`
|
||||
If set to a number n > 1, only every nth line number is printed.
|
||||
|
||||
`linenowidth`
|
||||
Maximum width devoted to line numbers (default: ``3*ystep``, sufficient
|
||||
for up to 4-digit line numbers. Increase width for longer code blocks).
|
||||
|
||||
`xoffset`
|
||||
Starting offset in X direction, defaults to ``0``.
|
||||
|
||||
`yoffset`
|
||||
Starting offset in Y direction, defaults to the font size if it is given
|
||||
in pixels, or ``20`` else. (This is necessary since text coordinates
|
||||
refer to the text baseline, not the top edge.)
|
||||
|
||||
`ystep`
|
||||
Offset to add to the Y coordinate for each subsequent line. This should
|
||||
roughly be the text size plus 5. It defaults to that value if the text
|
||||
size is given in pixels, or ``25`` else.
|
||||
|
||||
`spacehack`
|
||||
Convert spaces in the source to `` ``, which are non-breaking
|
||||
spaces. SVG provides the ``xml:space`` attribute to control how
|
||||
whitespace inside tags is handled, in theory, the ``preserve`` value
|
||||
could be used to keep all whitespace as-is. However, many current SVG
|
||||
viewers don't obey that rule, so this option is provided as a workaround
|
||||
and defaults to ``True``.
|
||||
"""
|
||||
name = 'SVG'
|
||||
aliases = ['svg']
|
||||
filenames = ['*.svg']
|
||||
|
||||
def __init__(self, **options):
|
||||
Formatter.__init__(self, **options)
|
||||
self.nowrap = get_bool_opt(options, 'nowrap', False)
|
||||
self.fontfamily = options.get('fontfamily', 'monospace')
|
||||
self.fontsize = options.get('fontsize', '14px')
|
||||
self.xoffset = get_int_opt(options, 'xoffset', 0)
|
||||
fs = self.fontsize.strip()
|
||||
if fs.endswith('px'): fs = fs[:-2].strip()
|
||||
try:
|
||||
int_fs = int(fs)
|
||||
except:
|
||||
int_fs = 20
|
||||
self.yoffset = get_int_opt(options, 'yoffset', int_fs)
|
||||
self.ystep = get_int_opt(options, 'ystep', int_fs + 5)
|
||||
self.spacehack = get_bool_opt(options, 'spacehack', True)
|
||||
self.linenos = get_bool_opt(options,'linenos',False)
|
||||
self.linenostart = get_int_opt(options,'linenostart',1)
|
||||
self.linenostep = get_int_opt(options,'linenostep',1)
|
||||
self.linenowidth = get_int_opt(options,'linenowidth', 3*self.ystep)
|
||||
self._stylecache = {}
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
"""
|
||||
Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
|
||||
tuples and write it into ``outfile``.
|
||||
|
||||
For our implementation we put all lines in their own 'line group'.
|
||||
"""
|
||||
x = self.xoffset
|
||||
y = self.yoffset
|
||||
if not self.nowrap:
|
||||
if self.encoding:
|
||||
outfile.write('<?xml version="1.0" encoding="%s"?>\n' %
|
||||
self.encoding)
|
||||
else:
|
||||
outfile.write('<?xml version="1.0"?>\n')
|
||||
outfile.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" '
|
||||
'"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/'
|
||||
'svg10.dtd">\n')
|
||||
outfile.write('<svg xmlns="http://www.w3.org/2000/svg">\n')
|
||||
outfile.write('<g font-family="%s" font-size="%s">\n' %
|
||||
(self.fontfamily, self.fontsize))
|
||||
|
||||
counter = self.linenostart
|
||||
counter_step = self.linenostep
|
||||
counter_style = self._get_style(Comment)
|
||||
line_x = x
|
||||
|
||||
if self.linenos:
|
||||
if counter % counter_step == 0:
|
||||
outfile.write('<text x="%s" y="%s" %s text-anchor="end">%s</text>' %
|
||||
(x+self.linenowidth,y,counter_style,counter))
|
||||
line_x += self.linenowidth + self.ystep
|
||||
counter += 1
|
||||
|
||||
outfile.write('<text x="%s" y="%s" xml:space="preserve">' % (line_x, y))
|
||||
for ttype, value in tokensource:
|
||||
style = self._get_style(ttype)
|
||||
tspan = style and '<tspan' + style + '>' or ''
|
||||
tspanend = tspan and '</tspan>' or ''
|
||||
value = escape_html(value)
|
||||
if self.spacehack:
|
||||
value = value.expandtabs().replace(' ', ' ')
|
||||
parts = value.split('\n')
|
||||
for part in parts[:-1]:
|
||||
outfile.write(tspan + part + tspanend)
|
||||
y += self.ystep
|
||||
outfile.write('</text>\n')
|
||||
if self.linenos and counter % counter_step == 0:
|
||||
outfile.write('<text x="%s" y="%s" text-anchor="end" %s>%s</text>' %
|
||||
(x+self.linenowidth,y,counter_style,counter))
|
||||
|
||||
counter += 1
|
||||
outfile.write('<text x="%s" y="%s" ' 'xml:space="preserve">' % (line_x,y))
|
||||
outfile.write(tspan + parts[-1] + tspanend)
|
||||
outfile.write('</text>')
|
||||
|
||||
if not self.nowrap:
|
||||
outfile.write('</g></svg>\n')
|
||||
|
||||
def _get_style(self, tokentype):
|
||||
if tokentype in self._stylecache:
|
||||
return self._stylecache[tokentype]
|
||||
otokentype = tokentype
|
||||
while not self.style.styles_token(tokentype):
|
||||
tokentype = tokentype.parent
|
||||
value = self.style.style_for_token(tokentype)
|
||||
result = ''
|
||||
if value['color']:
|
||||
result = ' fill="#' + value['color'] + '"'
|
||||
if value['bold']:
|
||||
result += ' font-weight="bold"'
|
||||
if value['italic']:
|
||||
result += ' font-style="italic"'
|
||||
self._stylecache[otokentype] = result
|
||||
return result
|
||||
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
pygments.formatters.terminal
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for terminal output with ANSI sequences.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
from pip._vendor.pygments.token import Keyword, Name, Comment, String, Error, \
|
||||
Number, Operator, Generic, Token, Whitespace
|
||||
from pip._vendor.pygments.console import ansiformat
|
||||
from pip._vendor.pygments.util import get_choice_opt
|
||||
|
||||
|
||||
__all__ = ['TerminalFormatter']
|
||||
|
||||
|
||||
#: Map token types to a tuple of color values for light and dark
|
||||
#: backgrounds.
|
||||
TERMINAL_COLORS = {
|
||||
Token: ('', ''),
|
||||
|
||||
Whitespace: ('gray', 'brightblack'),
|
||||
Comment: ('gray', 'brightblack'),
|
||||
Comment.Preproc: ('cyan', 'brightcyan'),
|
||||
Keyword: ('blue', 'brightblue'),
|
||||
Keyword.Type: ('cyan', 'brightcyan'),
|
||||
Operator.Word: ('magenta', 'brightmagenta'),
|
||||
Name.Builtin: ('cyan', 'brightcyan'),
|
||||
Name.Function: ('green', 'brightgreen'),
|
||||
Name.Namespace: ('_cyan_', '_brightcyan_'),
|
||||
Name.Class: ('_green_', '_brightgreen_'),
|
||||
Name.Exception: ('cyan', 'brightcyan'),
|
||||
Name.Decorator: ('brightblack', 'gray'),
|
||||
Name.Variable: ('red', 'brightred'),
|
||||
Name.Constant: ('red', 'brightred'),
|
||||
Name.Attribute: ('cyan', 'brightcyan'),
|
||||
Name.Tag: ('brightblue', 'brightblue'),
|
||||
String: ('yellow', 'yellow'),
|
||||
Number: ('blue', 'brightblue'),
|
||||
|
||||
Generic.Deleted: ('brightred', 'brightred'),
|
||||
Generic.Inserted: ('green', 'brightgreen'),
|
||||
Generic.Heading: ('**', '**'),
|
||||
Generic.Subheading: ('*magenta*', '*brightmagenta*'),
|
||||
Generic.Prompt: ('**', '**'),
|
||||
Generic.Error: ('brightred', 'brightred'),
|
||||
|
||||
Error: ('_brightred_', '_brightred_'),
|
||||
}
|
||||
|
||||
|
||||
class TerminalFormatter(Formatter):
|
||||
r"""
|
||||
Format tokens with ANSI color sequences, for output in a text console.
|
||||
Color sequences are terminated at newlines, so that paging the output
|
||||
works correctly.
|
||||
|
||||
The `get_style_defs()` method doesn't do anything special since there is
|
||||
no support for common styles.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`bg`
|
||||
Set to ``"light"`` or ``"dark"`` depending on the terminal's background
|
||||
(default: ``"light"``).
|
||||
|
||||
`colorscheme`
|
||||
A dictionary mapping token types to (lightbg, darkbg) color names or
|
||||
``None`` (default: ``None`` = use builtin colorscheme).
|
||||
|
||||
`linenos`
|
||||
Set to ``True`` to have line numbers on the terminal output as well
|
||||
(default: ``False`` = no line numbers).
|
||||
"""
|
||||
name = 'Terminal'
|
||||
aliases = ['terminal', 'console']
|
||||
filenames = []
|
||||
|
||||
def __init__(self, **options):
|
||||
Formatter.__init__(self, **options)
|
||||
self.darkbg = get_choice_opt(options, 'bg',
|
||||
['light', 'dark'], 'light') == 'dark'
|
||||
self.colorscheme = options.get('colorscheme', None) or TERMINAL_COLORS
|
||||
self.linenos = options.get('linenos', False)
|
||||
self._lineno = 0
|
||||
|
||||
def format(self, tokensource, outfile):
|
||||
return Formatter.format(self, tokensource, outfile)
|
||||
|
||||
def _write_lineno(self, outfile):
|
||||
self._lineno += 1
|
||||
outfile.write("%s%04d: " % (self._lineno != 1 and '\n' or '', self._lineno))
|
||||
|
||||
def _get_color(self, ttype):
|
||||
# self.colorscheme is a dict containing usually generic types, so we
|
||||
# have to walk the tree of dots. The base Token type must be a key,
|
||||
# even if it's empty string, as in the default above.
|
||||
colors = self.colorscheme.get(ttype)
|
||||
while colors is None:
|
||||
ttype = ttype.parent
|
||||
colors = self.colorscheme.get(ttype)
|
||||
return colors[self.darkbg]
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
if self.linenos:
|
||||
self._write_lineno(outfile)
|
||||
|
||||
for ttype, value in tokensource:
|
||||
color = self._get_color(ttype)
|
||||
|
||||
for line in value.splitlines(True):
|
||||
if color:
|
||||
outfile.write(ansiformat(color, line.rstrip('\n')))
|
||||
else:
|
||||
outfile.write(line.rstrip('\n'))
|
||||
if line.endswith('\n'):
|
||||
if self.linenos:
|
||||
self._write_lineno(outfile)
|
||||
else:
|
||||
outfile.write('\n')
|
||||
|
||||
if self.linenos:
|
||||
outfile.write("\n")
|
||||
@@ -0,0 +1,338 @@
|
||||
"""
|
||||
pygments.formatters.terminal256
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for 256-color terminal output with ANSI sequences.
|
||||
|
||||
RGB-to-XTERM color conversion routines adapted from xterm256-conv
|
||||
tool (http://frexx.de/xterm-256-notes/data/xterm256-conv2.tar.bz2)
|
||||
by Wolfgang Frisch.
|
||||
|
||||
Formatter version 1.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
# TODO:
|
||||
# - Options to map style's bold/underline/italic/border attributes
|
||||
# to some ANSI attrbutes (something like 'italic=underline')
|
||||
# - An option to output "style RGB to xterm RGB/index" conversion table
|
||||
# - An option to indicate that we are running in "reverse background"
|
||||
# xterm. This means that default colors are white-on-black, not
|
||||
# black-on-while, so colors like "white background" need to be converted
|
||||
# to "white background, black foreground", etc...
|
||||
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
from pip._vendor.pygments.console import codes
|
||||
from pip._vendor.pygments.style import ansicolors
|
||||
|
||||
|
||||
__all__ = ['Terminal256Formatter', 'TerminalTrueColorFormatter']
|
||||
|
||||
|
||||
class EscapeSequence:
|
||||
def __init__(self, fg=None, bg=None, bold=False, underline=False, italic=False):
|
||||
self.fg = fg
|
||||
self.bg = bg
|
||||
self.bold = bold
|
||||
self.underline = underline
|
||||
self.italic = italic
|
||||
|
||||
def escape(self, attrs):
|
||||
if len(attrs):
|
||||
return "\x1b[" + ";".join(attrs) + "m"
|
||||
return ""
|
||||
|
||||
def color_string(self):
|
||||
attrs = []
|
||||
if self.fg is not None:
|
||||
if self.fg in ansicolors:
|
||||
esc = codes[self.fg.replace('ansi','')]
|
||||
if ';01m' in esc:
|
||||
self.bold = True
|
||||
# extract fg color code.
|
||||
attrs.append(esc[2:4])
|
||||
else:
|
||||
attrs.extend(("38", "5", "%i" % self.fg))
|
||||
if self.bg is not None:
|
||||
if self.bg in ansicolors:
|
||||
esc = codes[self.bg.replace('ansi','')]
|
||||
# extract fg color code, add 10 for bg.
|
||||
attrs.append(str(int(esc[2:4])+10))
|
||||
else:
|
||||
attrs.extend(("48", "5", "%i" % self.bg))
|
||||
if self.bold:
|
||||
attrs.append("01")
|
||||
if self.underline:
|
||||
attrs.append("04")
|
||||
if self.italic:
|
||||
attrs.append("03")
|
||||
return self.escape(attrs)
|
||||
|
||||
def true_color_string(self):
|
||||
attrs = []
|
||||
if self.fg:
|
||||
attrs.extend(("38", "2", str(self.fg[0]), str(self.fg[1]), str(self.fg[2])))
|
||||
if self.bg:
|
||||
attrs.extend(("48", "2", str(self.bg[0]), str(self.bg[1]), str(self.bg[2])))
|
||||
if self.bold:
|
||||
attrs.append("01")
|
||||
if self.underline:
|
||||
attrs.append("04")
|
||||
if self.italic:
|
||||
attrs.append("03")
|
||||
return self.escape(attrs)
|
||||
|
||||
def reset_string(self):
|
||||
attrs = []
|
||||
if self.fg is not None:
|
||||
attrs.append("39")
|
||||
if self.bg is not None:
|
||||
attrs.append("49")
|
||||
if self.bold or self.underline or self.italic:
|
||||
attrs.append("00")
|
||||
return self.escape(attrs)
|
||||
|
||||
|
||||
class Terminal256Formatter(Formatter):
|
||||
"""
|
||||
Format tokens with ANSI color sequences, for output in a 256-color
|
||||
terminal or console. Like in `TerminalFormatter` color sequences
|
||||
are terminated at newlines, so that paging the output works correctly.
|
||||
|
||||
The formatter takes colors from a style defined by the `style` option
|
||||
and converts them to nearest ANSI 256-color escape sequences. Bold and
|
||||
underline attributes from the style are preserved (and displayed).
|
||||
|
||||
.. versionadded:: 0.9
|
||||
|
||||
.. versionchanged:: 2.2
|
||||
If the used style defines foreground colors in the form ``#ansi*``, then
|
||||
`Terminal256Formatter` will map these to non extended foreground color.
|
||||
See :ref:`AnsiTerminalStyle` for more information.
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
The ANSI color names have been updated with names that are easier to
|
||||
understand and align with colornames of other projects and terminals.
|
||||
See :ref:`this table <new-ansi-color-names>` for more information.
|
||||
|
||||
|
||||
Options accepted:
|
||||
|
||||
`style`
|
||||
The style to use, can be a string or a Style subclass (default:
|
||||
``'default'``).
|
||||
|
||||
`linenos`
|
||||
Set to ``True`` to have line numbers on the terminal output as well
|
||||
(default: ``False`` = no line numbers).
|
||||
"""
|
||||
name = 'Terminal256'
|
||||
aliases = ['terminal256', 'console256', '256']
|
||||
filenames = []
|
||||
|
||||
def __init__(self, **options):
|
||||
Formatter.__init__(self, **options)
|
||||
|
||||
self.xterm_colors = []
|
||||
self.best_match = {}
|
||||
self.style_string = {}
|
||||
|
||||
self.usebold = 'nobold' not in options
|
||||
self.useunderline = 'nounderline' not in options
|
||||
self.useitalic = 'noitalic' not in options
|
||||
|
||||
self._build_color_table() # build an RGB-to-256 color conversion table
|
||||
self._setup_styles() # convert selected style's colors to term. colors
|
||||
|
||||
self.linenos = options.get('linenos', False)
|
||||
self._lineno = 0
|
||||
|
||||
def _build_color_table(self):
|
||||
# colors 0..15: 16 basic colors
|
||||
|
||||
self.xterm_colors.append((0x00, 0x00, 0x00)) # 0
|
||||
self.xterm_colors.append((0xcd, 0x00, 0x00)) # 1
|
||||
self.xterm_colors.append((0x00, 0xcd, 0x00)) # 2
|
||||
self.xterm_colors.append((0xcd, 0xcd, 0x00)) # 3
|
||||
self.xterm_colors.append((0x00, 0x00, 0xee)) # 4
|
||||
self.xterm_colors.append((0xcd, 0x00, 0xcd)) # 5
|
||||
self.xterm_colors.append((0x00, 0xcd, 0xcd)) # 6
|
||||
self.xterm_colors.append((0xe5, 0xe5, 0xe5)) # 7
|
||||
self.xterm_colors.append((0x7f, 0x7f, 0x7f)) # 8
|
||||
self.xterm_colors.append((0xff, 0x00, 0x00)) # 9
|
||||
self.xterm_colors.append((0x00, 0xff, 0x00)) # 10
|
||||
self.xterm_colors.append((0xff, 0xff, 0x00)) # 11
|
||||
self.xterm_colors.append((0x5c, 0x5c, 0xff)) # 12
|
||||
self.xterm_colors.append((0xff, 0x00, 0xff)) # 13
|
||||
self.xterm_colors.append((0x00, 0xff, 0xff)) # 14
|
||||
self.xterm_colors.append((0xff, 0xff, 0xff)) # 15
|
||||
|
||||
# colors 16..232: the 6x6x6 color cube
|
||||
|
||||
valuerange = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff)
|
||||
|
||||
for i in range(217):
|
||||
r = valuerange[(i // 36) % 6]
|
||||
g = valuerange[(i // 6) % 6]
|
||||
b = valuerange[i % 6]
|
||||
self.xterm_colors.append((r, g, b))
|
||||
|
||||
# colors 233..253: grayscale
|
||||
|
||||
for i in range(1, 22):
|
||||
v = 8 + i * 10
|
||||
self.xterm_colors.append((v, v, v))
|
||||
|
||||
def _closest_color(self, r, g, b):
|
||||
distance = 257*257*3 # "infinity" (>distance from #000000 to #ffffff)
|
||||
match = 0
|
||||
|
||||
for i in range(0, 254):
|
||||
values = self.xterm_colors[i]
|
||||
|
||||
rd = r - values[0]
|
||||
gd = g - values[1]
|
||||
bd = b - values[2]
|
||||
d = rd*rd + gd*gd + bd*bd
|
||||
|
||||
if d < distance:
|
||||
match = i
|
||||
distance = d
|
||||
return match
|
||||
|
||||
def _color_index(self, color):
|
||||
index = self.best_match.get(color, None)
|
||||
if color in ansicolors:
|
||||
# strip the `ansi/#ansi` part and look up code
|
||||
index = color
|
||||
self.best_match[color] = index
|
||||
if index is None:
|
||||
try:
|
||||
rgb = int(str(color), 16)
|
||||
except ValueError:
|
||||
rgb = 0
|
||||
|
||||
r = (rgb >> 16) & 0xff
|
||||
g = (rgb >> 8) & 0xff
|
||||
b = rgb & 0xff
|
||||
index = self._closest_color(r, g, b)
|
||||
self.best_match[color] = index
|
||||
return index
|
||||
|
||||
def _setup_styles(self):
|
||||
for ttype, ndef in self.style:
|
||||
escape = EscapeSequence()
|
||||
# get foreground from ansicolor if set
|
||||
if ndef['ansicolor']:
|
||||
escape.fg = self._color_index(ndef['ansicolor'])
|
||||
elif ndef['color']:
|
||||
escape.fg = self._color_index(ndef['color'])
|
||||
if ndef['bgansicolor']:
|
||||
escape.bg = self._color_index(ndef['bgansicolor'])
|
||||
elif ndef['bgcolor']:
|
||||
escape.bg = self._color_index(ndef['bgcolor'])
|
||||
if self.usebold and ndef['bold']:
|
||||
escape.bold = True
|
||||
if self.useunderline and ndef['underline']:
|
||||
escape.underline = True
|
||||
if self.useitalic and ndef['italic']:
|
||||
escape.italic = True
|
||||
self.style_string[str(ttype)] = (escape.color_string(),
|
||||
escape.reset_string())
|
||||
|
||||
def _write_lineno(self, outfile):
|
||||
self._lineno += 1
|
||||
outfile.write("%s%04d: " % (self._lineno != 1 and '\n' or '', self._lineno))
|
||||
|
||||
def format(self, tokensource, outfile):
|
||||
return Formatter.format(self, tokensource, outfile)
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
if self.linenos:
|
||||
self._write_lineno(outfile)
|
||||
|
||||
for ttype, value in tokensource:
|
||||
not_found = True
|
||||
while ttype and not_found:
|
||||
try:
|
||||
# outfile.write( "<" + str(ttype) + ">" )
|
||||
on, off = self.style_string[str(ttype)]
|
||||
|
||||
# Like TerminalFormatter, add "reset colors" escape sequence
|
||||
# on newline.
|
||||
spl = value.split('\n')
|
||||
for line in spl[:-1]:
|
||||
if line:
|
||||
outfile.write(on + line + off)
|
||||
if self.linenos:
|
||||
self._write_lineno(outfile)
|
||||
else:
|
||||
outfile.write('\n')
|
||||
|
||||
if spl[-1]:
|
||||
outfile.write(on + spl[-1] + off)
|
||||
|
||||
not_found = False
|
||||
# outfile.write( '#' + str(ttype) + '#' )
|
||||
|
||||
except KeyError:
|
||||
# ottype = ttype
|
||||
ttype = ttype.parent
|
||||
# outfile.write( '!' + str(ottype) + '->' + str(ttype) + '!' )
|
||||
|
||||
if not_found:
|
||||
outfile.write(value)
|
||||
|
||||
if self.linenos:
|
||||
outfile.write("\n")
|
||||
|
||||
|
||||
|
||||
class TerminalTrueColorFormatter(Terminal256Formatter):
|
||||
r"""
|
||||
Format tokens with ANSI color sequences, for output in a true-color
|
||||
terminal or console. Like in `TerminalFormatter` color sequences
|
||||
are terminated at newlines, so that paging the output works correctly.
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
Options accepted:
|
||||
|
||||
`style`
|
||||
The style to use, can be a string or a Style subclass (default:
|
||||
``'default'``).
|
||||
"""
|
||||
name = 'TerminalTrueColor'
|
||||
aliases = ['terminal16m', 'console16m', '16m']
|
||||
filenames = []
|
||||
|
||||
def _build_color_table(self):
|
||||
pass
|
||||
|
||||
def _color_tuple(self, color):
|
||||
try:
|
||||
rgb = int(str(color), 16)
|
||||
except ValueError:
|
||||
return None
|
||||
r = (rgb >> 16) & 0xff
|
||||
g = (rgb >> 8) & 0xff
|
||||
b = rgb & 0xff
|
||||
return (r, g, b)
|
||||
|
||||
def _setup_styles(self):
|
||||
for ttype, ndef in self.style:
|
||||
escape = EscapeSequence()
|
||||
if ndef['color']:
|
||||
escape.fg = self._color_tuple(ndef['color'])
|
||||
if ndef['bgcolor']:
|
||||
escape.bg = self._color_tuple(ndef['bgcolor'])
|
||||
if self.usebold and ndef['bold']:
|
||||
escape.bold = True
|
||||
if self.useunderline and ndef['underline']:
|
||||
escape.underline = True
|
||||
if self.useitalic and ndef['italic']:
|
||||
escape.italic = True
|
||||
self.style_string[str(ttype)] = (escape.true_color_string(),
|
||||
escape.reset_string())
|
||||
@@ -0,0 +1,943 @@
|
||||
"""
|
||||
pygments.lexer
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Base lexer classes.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
from pip._vendor.pygments.filter import apply_filters, Filter
|
||||
from pip._vendor.pygments.filters import get_filter_by_name
|
||||
from pip._vendor.pygments.token import Error, Text, Other, Whitespace, _TokenType
|
||||
from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt, \
|
||||
make_analysator, Future, guess_decode
|
||||
from pip._vendor.pygments.regexopt import regex_opt
|
||||
|
||||
__all__ = ['Lexer', 'RegexLexer', 'ExtendedRegexLexer', 'DelegatingLexer',
|
||||
'LexerContext', 'include', 'inherit', 'bygroups', 'using', 'this',
|
||||
'default', 'words', 'line_re']
|
||||
|
||||
line_re = re.compile('.*?\n')
|
||||
|
||||
_encoding_map = [(b'\xef\xbb\xbf', 'utf-8'),
|
||||
(b'\xff\xfe\0\0', 'utf-32'),
|
||||
(b'\0\0\xfe\xff', 'utf-32be'),
|
||||
(b'\xff\xfe', 'utf-16'),
|
||||
(b'\xfe\xff', 'utf-16be')]
|
||||
|
||||
_default_analyse = staticmethod(lambda x: 0.0)
|
||||
|
||||
|
||||
class LexerMeta(type):
|
||||
"""
|
||||
This metaclass automagically converts ``analyse_text`` methods into
|
||||
static methods which always return float values.
|
||||
"""
|
||||
|
||||
def __new__(mcs, name, bases, d):
|
||||
if 'analyse_text' in d:
|
||||
d['analyse_text'] = make_analysator(d['analyse_text'])
|
||||
return type.__new__(mcs, name, bases, d)
|
||||
|
||||
|
||||
class Lexer(metaclass=LexerMeta):
|
||||
"""
|
||||
Lexer for a specific language.
|
||||
|
||||
See also :doc:`lexerdevelopment`, a high-level guide to writing
|
||||
lexers.
|
||||
|
||||
Lexer classes have attributes used for choosing the most appropriate
|
||||
lexer based on various criteria.
|
||||
|
||||
.. autoattribute:: name
|
||||
:no-value:
|
||||
.. autoattribute:: aliases
|
||||
:no-value:
|
||||
.. autoattribute:: filenames
|
||||
:no-value:
|
||||
.. autoattribute:: alias_filenames
|
||||
.. autoattribute:: mimetypes
|
||||
:no-value:
|
||||
.. autoattribute:: priority
|
||||
|
||||
Lexers included in Pygments should have an additional attribute:
|
||||
|
||||
.. autoattribute:: url
|
||||
:no-value:
|
||||
|
||||
You can pass options to the constructor. The basic options recognized
|
||||
by all lexers and processed by the base `Lexer` class are:
|
||||
|
||||
``stripnl``
|
||||
Strip leading and trailing newlines from the input (default: True).
|
||||
``stripall``
|
||||
Strip all leading and trailing whitespace from the input
|
||||
(default: False).
|
||||
``ensurenl``
|
||||
Make sure that the input ends with a newline (default: True). This
|
||||
is required for some lexers that consume input linewise.
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
``tabsize``
|
||||
If given and greater than 0, expand tabs in the input (default: 0).
|
||||
``encoding``
|
||||
If given, must be an encoding name. This encoding will be used to
|
||||
convert the input string to Unicode, if it is not already a Unicode
|
||||
string (default: ``'guess'``, which uses a simple UTF-8 / Locale /
|
||||
Latin1 detection. Can also be ``'chardet'`` to use the chardet
|
||||
library, if it is installed.
|
||||
``inencoding``
|
||||
Overrides the ``encoding`` if given.
|
||||
"""
|
||||
|
||||
#: Full name of the lexer, in human-readable form
|
||||
name = None
|
||||
|
||||
#: A list of short, unique identifiers that can be used to look
|
||||
#: up the lexer from a list, e.g., using `get_lexer_by_name()`.
|
||||
aliases = []
|
||||
|
||||
#: A list of `fnmatch` patterns that match filenames which contain
|
||||
#: content for this lexer. The patterns in this list should be unique among
|
||||
#: all lexers.
|
||||
filenames = []
|
||||
|
||||
#: A list of `fnmatch` patterns that match filenames which may or may not
|
||||
#: contain content for this lexer. This list is used by the
|
||||
#: :func:`.guess_lexer_for_filename()` function, to determine which lexers
|
||||
#: are then included in guessing the correct one. That means that
|
||||
#: e.g. every lexer for HTML and a template language should include
|
||||
#: ``\*.html`` in this list.
|
||||
alias_filenames = []
|
||||
|
||||
#: A list of MIME types for content that can be lexed with this lexer.
|
||||
mimetypes = []
|
||||
|
||||
#: Priority, should multiple lexers match and no content is provided
|
||||
priority = 0
|
||||
|
||||
#: URL of the language specification/definition. Used in the Pygments
|
||||
#: documentation.
|
||||
url = None
|
||||
|
||||
def __init__(self, **options):
|
||||
"""
|
||||
This constructor takes arbitrary options as keyword arguments.
|
||||
Every subclass must first process its own options and then call
|
||||
the `Lexer` constructor, since it processes the basic
|
||||
options like `stripnl`.
|
||||
|
||||
An example looks like this:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def __init__(self, **options):
|
||||
self.compress = options.get('compress', '')
|
||||
Lexer.__init__(self, **options)
|
||||
|
||||
As these options must all be specifiable as strings (due to the
|
||||
command line usage), there are various utility functions
|
||||
available to help with that, see `Utilities`_.
|
||||
"""
|
||||
self.options = options
|
||||
self.stripnl = get_bool_opt(options, 'stripnl', True)
|
||||
self.stripall = get_bool_opt(options, 'stripall', False)
|
||||
self.ensurenl = get_bool_opt(options, 'ensurenl', True)
|
||||
self.tabsize = get_int_opt(options, 'tabsize', 0)
|
||||
self.encoding = options.get('encoding', 'guess')
|
||||
self.encoding = options.get('inencoding') or self.encoding
|
||||
self.filters = []
|
||||
for filter_ in get_list_opt(options, 'filters', ()):
|
||||
self.add_filter(filter_)
|
||||
|
||||
def __repr__(self):
|
||||
if self.options:
|
||||
return '<pygments.lexers.%s with %r>' % (self.__class__.__name__,
|
||||
self.options)
|
||||
else:
|
||||
return '<pygments.lexers.%s>' % self.__class__.__name__
|
||||
|
||||
def add_filter(self, filter_, **options):
|
||||
"""
|
||||
Add a new stream filter to this lexer.
|
||||
"""
|
||||
if not isinstance(filter_, Filter):
|
||||
filter_ = get_filter_by_name(filter_, **options)
|
||||
self.filters.append(filter_)
|
||||
|
||||
def analyse_text(text):
|
||||
"""
|
||||
A static method which is called for lexer guessing.
|
||||
|
||||
It should analyse the text and return a float in the range
|
||||
from ``0.0`` to ``1.0``. If it returns ``0.0``, the lexer
|
||||
will not be selected as the most probable one, if it returns
|
||||
``1.0``, it will be selected immediately. This is used by
|
||||
`guess_lexer`.
|
||||
|
||||
The `LexerMeta` metaclass automatically wraps this function so
|
||||
that it works like a static method (no ``self`` or ``cls``
|
||||
parameter) and the return value is automatically converted to
|
||||
`float`. If the return value is an object that is boolean `False`
|
||||
it's the same as if the return values was ``0.0``.
|
||||
"""
|
||||
|
||||
def get_tokens(self, text, unfiltered=False):
|
||||
"""
|
||||
This method is the basic interface of a lexer. It is called by
|
||||
the `highlight()` function. It must process the text and return an
|
||||
iterable of ``(tokentype, value)`` pairs from `text`.
|
||||
|
||||
Normally, you don't need to override this method. The default
|
||||
implementation processes the options recognized by all lexers
|
||||
(`stripnl`, `stripall` and so on), and then yields all tokens
|
||||
from `get_tokens_unprocessed()`, with the ``index`` dropped.
|
||||
|
||||
If `unfiltered` is set to `True`, the filtering mechanism is
|
||||
bypassed even if filters are defined.
|
||||
"""
|
||||
if not isinstance(text, str):
|
||||
if self.encoding == 'guess':
|
||||
text, _ = guess_decode(text)
|
||||
elif self.encoding == 'chardet':
|
||||
try:
|
||||
from pip._vendor import chardet
|
||||
except ImportError as e:
|
||||
raise ImportError('To enable chardet encoding guessing, '
|
||||
'please install the chardet library '
|
||||
'from http://chardet.feedparser.org/') from e
|
||||
# check for BOM first
|
||||
decoded = None
|
||||
for bom, encoding in _encoding_map:
|
||||
if text.startswith(bom):
|
||||
decoded = text[len(bom):].decode(encoding, 'replace')
|
||||
break
|
||||
# no BOM found, so use chardet
|
||||
if decoded is None:
|
||||
enc = chardet.detect(text[:1024]) # Guess using first 1KB
|
||||
decoded = text.decode(enc.get('encoding') or 'utf-8',
|
||||
'replace')
|
||||
text = decoded
|
||||
else:
|
||||
text = text.decode(self.encoding)
|
||||
if text.startswith('\ufeff'):
|
||||
text = text[len('\ufeff'):]
|
||||
else:
|
||||
if text.startswith('\ufeff'):
|
||||
text = text[len('\ufeff'):]
|
||||
|
||||
# text now *is* a unicode string
|
||||
text = text.replace('\r\n', '\n')
|
||||
text = text.replace('\r', '\n')
|
||||
if self.stripall:
|
||||
text = text.strip()
|
||||
elif self.stripnl:
|
||||
text = text.strip('\n')
|
||||
if self.tabsize > 0:
|
||||
text = text.expandtabs(self.tabsize)
|
||||
if self.ensurenl and not text.endswith('\n'):
|
||||
text += '\n'
|
||||
|
||||
def streamer():
|
||||
for _, t, v in self.get_tokens_unprocessed(text):
|
||||
yield t, v
|
||||
stream = streamer()
|
||||
if not unfiltered:
|
||||
stream = apply_filters(stream, self.filters, self)
|
||||
return stream
|
||||
|
||||
def get_tokens_unprocessed(self, text):
|
||||
"""
|
||||
This method should process the text and return an iterable of
|
||||
``(index, tokentype, value)`` tuples where ``index`` is the starting
|
||||
position of the token within the input text.
|
||||
|
||||
It must be overridden by subclasses. It is recommended to
|
||||
implement it as a generator to maximize effectiveness.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DelegatingLexer(Lexer):
|
||||
"""
|
||||
This lexer takes two lexer as arguments. A root lexer and
|
||||
a language lexer. First everything is scanned using the language
|
||||
lexer, afterwards all ``Other`` tokens are lexed using the root
|
||||
lexer.
|
||||
|
||||
The lexers from the ``template`` lexer package use this base lexer.
|
||||
"""
|
||||
|
||||
def __init__(self, _root_lexer, _language_lexer, _needle=Other, **options):
|
||||
self.root_lexer = _root_lexer(**options)
|
||||
self.language_lexer = _language_lexer(**options)
|
||||
self.needle = _needle
|
||||
Lexer.__init__(self, **options)
|
||||
|
||||
def get_tokens_unprocessed(self, text):
|
||||
buffered = ''
|
||||
insertions = []
|
||||
lng_buffer = []
|
||||
for i, t, v in self.language_lexer.get_tokens_unprocessed(text):
|
||||
if t is self.needle:
|
||||
if lng_buffer:
|
||||
insertions.append((len(buffered), lng_buffer))
|
||||
lng_buffer = []
|
||||
buffered += v
|
||||
else:
|
||||
lng_buffer.append((i, t, v))
|
||||
if lng_buffer:
|
||||
insertions.append((len(buffered), lng_buffer))
|
||||
return do_insertions(insertions,
|
||||
self.root_lexer.get_tokens_unprocessed(buffered))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# RegexLexer and ExtendedRegexLexer
|
||||
#
|
||||
|
||||
|
||||
class include(str): # pylint: disable=invalid-name
|
||||
"""
|
||||
Indicates that a state should include rules from another state.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class _inherit:
|
||||
"""
|
||||
Indicates the a state should inherit from its superclass.
|
||||
"""
|
||||
def __repr__(self):
|
||||
return 'inherit'
|
||||
|
||||
inherit = _inherit() # pylint: disable=invalid-name
|
||||
|
||||
|
||||
class combined(tuple): # pylint: disable=invalid-name
|
||||
"""
|
||||
Indicates a state combined from multiple states.
|
||||
"""
|
||||
|
||||
def __new__(cls, *args):
|
||||
return tuple.__new__(cls, args)
|
||||
|
||||
def __init__(self, *args):
|
||||
# tuple.__init__ doesn't do anything
|
||||
pass
|
||||
|
||||
|
||||
class _PseudoMatch:
|
||||
"""
|
||||
A pseudo match object constructed from a string.
|
||||
"""
|
||||
|
||||
def __init__(self, start, text):
|
||||
self._text = text
|
||||
self._start = start
|
||||
|
||||
def start(self, arg=None):
|
||||
return self._start
|
||||
|
||||
def end(self, arg=None):
|
||||
return self._start + len(self._text)
|
||||
|
||||
def group(self, arg=None):
|
||||
if arg:
|
||||
raise IndexError('No such group')
|
||||
return self._text
|
||||
|
||||
def groups(self):
|
||||
return (self._text,)
|
||||
|
||||
def groupdict(self):
|
||||
return {}
|
||||
|
||||
|
||||
def bygroups(*args):
|
||||
"""
|
||||
Callback that yields multiple actions for each group in the match.
|
||||
"""
|
||||
def callback(lexer, match, ctx=None):
|
||||
for i, action in enumerate(args):
|
||||
if action is None:
|
||||
continue
|
||||
elif type(action) is _TokenType:
|
||||
data = match.group(i + 1)
|
||||
if data:
|
||||
yield match.start(i + 1), action, data
|
||||
else:
|
||||
data = match.group(i + 1)
|
||||
if data is not None:
|
||||
if ctx:
|
||||
ctx.pos = match.start(i + 1)
|
||||
for item in action(lexer,
|
||||
_PseudoMatch(match.start(i + 1), data), ctx):
|
||||
if item:
|
||||
yield item
|
||||
if ctx:
|
||||
ctx.pos = match.end()
|
||||
return callback
|
||||
|
||||
|
||||
class _This:
|
||||
"""
|
||||
Special singleton used for indicating the caller class.
|
||||
Used by ``using``.
|
||||
"""
|
||||
|
||||
this = _This()
|
||||
|
||||
|
||||
def using(_other, **kwargs):
|
||||
"""
|
||||
Callback that processes the match with a different lexer.
|
||||
|
||||
The keyword arguments are forwarded to the lexer, except `state` which
|
||||
is handled separately.
|
||||
|
||||
`state` specifies the state that the new lexer will start in, and can
|
||||
be an enumerable such as ('root', 'inline', 'string') or a simple
|
||||
string which is assumed to be on top of the root state.
|
||||
|
||||
Note: For that to work, `_other` must not be an `ExtendedRegexLexer`.
|
||||
"""
|
||||
gt_kwargs = {}
|
||||
if 'state' in kwargs:
|
||||
s = kwargs.pop('state')
|
||||
if isinstance(s, (list, tuple)):
|
||||
gt_kwargs['stack'] = s
|
||||
else:
|
||||
gt_kwargs['stack'] = ('root', s)
|
||||
|
||||
if _other is this:
|
||||
def callback(lexer, match, ctx=None):
|
||||
# if keyword arguments are given the callback
|
||||
# function has to create a new lexer instance
|
||||
if kwargs:
|
||||
# XXX: cache that somehow
|
||||
kwargs.update(lexer.options)
|
||||
lx = lexer.__class__(**kwargs)
|
||||
else:
|
||||
lx = lexer
|
||||
s = match.start()
|
||||
for i, t, v in lx.get_tokens_unprocessed(match.group(), **gt_kwargs):
|
||||
yield i + s, t, v
|
||||
if ctx:
|
||||
ctx.pos = match.end()
|
||||
else:
|
||||
def callback(lexer, match, ctx=None):
|
||||
# XXX: cache that somehow
|
||||
kwargs.update(lexer.options)
|
||||
lx = _other(**kwargs)
|
||||
|
||||
s = match.start()
|
||||
for i, t, v in lx.get_tokens_unprocessed(match.group(), **gt_kwargs):
|
||||
yield i + s, t, v
|
||||
if ctx:
|
||||
ctx.pos = match.end()
|
||||
return callback
|
||||
|
||||
|
||||
class default:
|
||||
"""
|
||||
Indicates a state or state action (e.g. #pop) to apply.
|
||||
For example default('#pop') is equivalent to ('', Token, '#pop')
|
||||
Note that state tuples may be used as well.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
def __init__(self, state):
|
||||
self.state = state
|
||||
|
||||
|
||||
class words(Future):
|
||||
"""
|
||||
Indicates a list of literal words that is transformed into an optimized
|
||||
regex that matches any of the words.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
def __init__(self, words, prefix='', suffix=''):
|
||||
self.words = words
|
||||
self.prefix = prefix
|
||||
self.suffix = suffix
|
||||
|
||||
def get(self):
|
||||
return regex_opt(self.words, prefix=self.prefix, suffix=self.suffix)
|
||||
|
||||
|
||||
class RegexLexerMeta(LexerMeta):
|
||||
"""
|
||||
Metaclass for RegexLexer, creates the self._tokens attribute from
|
||||
self.tokens on the first instantiation.
|
||||
"""
|
||||
|
||||
def _process_regex(cls, regex, rflags, state):
|
||||
"""Preprocess the regular expression component of a token definition."""
|
||||
if isinstance(regex, Future):
|
||||
regex = regex.get()
|
||||
return re.compile(regex, rflags).match
|
||||
|
||||
def _process_token(cls, token):
|
||||
"""Preprocess the token component of a token definition."""
|
||||
assert type(token) is _TokenType or callable(token), \
|
||||
'token type must be simple type or callable, not %r' % (token,)
|
||||
return token
|
||||
|
||||
def _process_new_state(cls, new_state, unprocessed, processed):
|
||||
"""Preprocess the state transition action of a token definition."""
|
||||
if isinstance(new_state, str):
|
||||
# an existing state
|
||||
if new_state == '#pop':
|
||||
return -1
|
||||
elif new_state in unprocessed:
|
||||
return (new_state,)
|
||||
elif new_state == '#push':
|
||||
return new_state
|
||||
elif new_state[:5] == '#pop:':
|
||||
return -int(new_state[5:])
|
||||
else:
|
||||
assert False, 'unknown new state %r' % new_state
|
||||
elif isinstance(new_state, combined):
|
||||
# combine a new state from existing ones
|
||||
tmp_state = '_tmp_%d' % cls._tmpname
|
||||
cls._tmpname += 1
|
||||
itokens = []
|
||||
for istate in new_state:
|
||||
assert istate != new_state, 'circular state ref %r' % istate
|
||||
itokens.extend(cls._process_state(unprocessed,
|
||||
processed, istate))
|
||||
processed[tmp_state] = itokens
|
||||
return (tmp_state,)
|
||||
elif isinstance(new_state, tuple):
|
||||
# push more than one state
|
||||
for istate in new_state:
|
||||
assert (istate in unprocessed or
|
||||
istate in ('#pop', '#push')), \
|
||||
'unknown new state ' + istate
|
||||
return new_state
|
||||
else:
|
||||
assert False, 'unknown new state def %r' % new_state
|
||||
|
||||
def _process_state(cls, unprocessed, processed, state):
|
||||
"""Preprocess a single state definition."""
|
||||
assert type(state) is str, "wrong state name %r" % state
|
||||
assert state[0] != '#', "invalid state name %r" % state
|
||||
if state in processed:
|
||||
return processed[state]
|
||||
tokens = processed[state] = []
|
||||
rflags = cls.flags
|
||||
for tdef in unprocessed[state]:
|
||||
if isinstance(tdef, include):
|
||||
# it's a state reference
|
||||
assert tdef != state, "circular state reference %r" % state
|
||||
tokens.extend(cls._process_state(unprocessed, processed,
|
||||
str(tdef)))
|
||||
continue
|
||||
if isinstance(tdef, _inherit):
|
||||
# should be processed already, but may not in the case of:
|
||||
# 1. the state has no counterpart in any parent
|
||||
# 2. the state includes more than one 'inherit'
|
||||
continue
|
||||
if isinstance(tdef, default):
|
||||
new_state = cls._process_new_state(tdef.state, unprocessed, processed)
|
||||
tokens.append((re.compile('').match, None, new_state))
|
||||
continue
|
||||
|
||||
assert type(tdef) is tuple, "wrong rule def %r" % tdef
|
||||
|
||||
try:
|
||||
rex = cls._process_regex(tdef[0], rflags, state)
|
||||
except Exception as err:
|
||||
raise ValueError("uncompilable regex %r in state %r of %r: %s" %
|
||||
(tdef[0], state, cls, err)) from err
|
||||
|
||||
token = cls._process_token(tdef[1])
|
||||
|
||||
if len(tdef) == 2:
|
||||
new_state = None
|
||||
else:
|
||||
new_state = cls._process_new_state(tdef[2],
|
||||
unprocessed, processed)
|
||||
|
||||
tokens.append((rex, token, new_state))
|
||||
return tokens
|
||||
|
||||
def process_tokendef(cls, name, tokendefs=None):
|
||||
"""Preprocess a dictionary of token definitions."""
|
||||
processed = cls._all_tokens[name] = {}
|
||||
tokendefs = tokendefs or cls.tokens[name]
|
||||
for state in list(tokendefs):
|
||||
cls._process_state(tokendefs, processed, state)
|
||||
return processed
|
||||
|
||||
def get_tokendefs(cls):
|
||||
"""
|
||||
Merge tokens from superclasses in MRO order, returning a single tokendef
|
||||
dictionary.
|
||||
|
||||
Any state that is not defined by a subclass will be inherited
|
||||
automatically. States that *are* defined by subclasses will, by
|
||||
default, override that state in the superclass. If a subclass wishes to
|
||||
inherit definitions from a superclass, it can use the special value
|
||||
"inherit", which will cause the superclass' state definition to be
|
||||
included at that point in the state.
|
||||
"""
|
||||
tokens = {}
|
||||
inheritable = {}
|
||||
for c in cls.__mro__:
|
||||
toks = c.__dict__.get('tokens', {})
|
||||
|
||||
for state, items in toks.items():
|
||||
curitems = tokens.get(state)
|
||||
if curitems is None:
|
||||
# N.b. because this is assigned by reference, sufficiently
|
||||
# deep hierarchies are processed incrementally (e.g. for
|
||||
# A(B), B(C), C(RegexLexer), B will be premodified so X(B)
|
||||
# will not see any inherits in B).
|
||||
tokens[state] = items
|
||||
try:
|
||||
inherit_ndx = items.index(inherit)
|
||||
except ValueError:
|
||||
continue
|
||||
inheritable[state] = inherit_ndx
|
||||
continue
|
||||
|
||||
inherit_ndx = inheritable.pop(state, None)
|
||||
if inherit_ndx is None:
|
||||
continue
|
||||
|
||||
# Replace the "inherit" value with the items
|
||||
curitems[inherit_ndx:inherit_ndx+1] = items
|
||||
try:
|
||||
# N.b. this is the index in items (that is, the superclass
|
||||
# copy), so offset required when storing below.
|
||||
new_inh_ndx = items.index(inherit)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
inheritable[state] = inherit_ndx + new_inh_ndx
|
||||
|
||||
return tokens
|
||||
|
||||
def __call__(cls, *args, **kwds):
|
||||
"""Instantiate cls after preprocessing its token definitions."""
|
||||
if '_tokens' not in cls.__dict__:
|
||||
cls._all_tokens = {}
|
||||
cls._tmpname = 0
|
||||
if hasattr(cls, 'token_variants') and cls.token_variants:
|
||||
# don't process yet
|
||||
pass
|
||||
else:
|
||||
cls._tokens = cls.process_tokendef('', cls.get_tokendefs())
|
||||
|
||||
return type.__call__(cls, *args, **kwds)
|
||||
|
||||
|
||||
class RegexLexer(Lexer, metaclass=RegexLexerMeta):
|
||||
"""
|
||||
Base for simple stateful regular expression-based lexers.
|
||||
Simplifies the lexing process so that you need only
|
||||
provide a list of states and regular expressions.
|
||||
"""
|
||||
|
||||
#: Flags for compiling the regular expressions.
|
||||
#: Defaults to MULTILINE.
|
||||
flags = re.MULTILINE
|
||||
|
||||
#: At all time there is a stack of states. Initially, the stack contains
|
||||
#: a single state 'root'. The top of the stack is called "the current state".
|
||||
#:
|
||||
#: Dict of ``{'state': [(regex, tokentype, new_state), ...], ...}``
|
||||
#:
|
||||
#: ``new_state`` can be omitted to signify no state transition.
|
||||
#: If ``new_state`` is a string, it is pushed on the stack. This ensure
|
||||
#: the new current state is ``new_state``.
|
||||
#: If ``new_state`` is a tuple of strings, all of those strings are pushed
|
||||
#: on the stack and the current state will be the last element of the list.
|
||||
#: ``new_state`` can also be ``combined('state1', 'state2', ...)``
|
||||
#: to signify a new, anonymous state combined from the rules of two
|
||||
#: or more existing ones.
|
||||
#: Furthermore, it can be '#pop' to signify going back one step in
|
||||
#: the state stack, or '#push' to push the current state on the stack
|
||||
#: again. Note that if you push while in a combined state, the combined
|
||||
#: state itself is pushed, and not only the state in which the rule is
|
||||
#: defined.
|
||||
#:
|
||||
#: The tuple can also be replaced with ``include('state')``, in which
|
||||
#: case the rules from the state named by the string are included in the
|
||||
#: current one.
|
||||
tokens = {}
|
||||
|
||||
def get_tokens_unprocessed(self, text, stack=('root',)):
|
||||
"""
|
||||
Split ``text`` into (tokentype, text) pairs.
|
||||
|
||||
``stack`` is the initial stack (default: ``['root']``)
|
||||
"""
|
||||
pos = 0
|
||||
tokendefs = self._tokens
|
||||
statestack = list(stack)
|
||||
statetokens = tokendefs[statestack[-1]]
|
||||
while 1:
|
||||
for rexmatch, action, new_state in statetokens:
|
||||
m = rexmatch(text, pos)
|
||||
if m:
|
||||
if action is not None:
|
||||
if type(action) is _TokenType:
|
||||
yield pos, action, m.group()
|
||||
else:
|
||||
yield from action(self, m)
|
||||
pos = m.end()
|
||||
if new_state is not None:
|
||||
# state transition
|
||||
if isinstance(new_state, tuple):
|
||||
for state in new_state:
|
||||
if state == '#pop':
|
||||
if len(statestack) > 1:
|
||||
statestack.pop()
|
||||
elif state == '#push':
|
||||
statestack.append(statestack[-1])
|
||||
else:
|
||||
statestack.append(state)
|
||||
elif isinstance(new_state, int):
|
||||
# pop, but keep at least one state on the stack
|
||||
# (random code leading to unexpected pops should
|
||||
# not allow exceptions)
|
||||
if abs(new_state) >= len(statestack):
|
||||
del statestack[1:]
|
||||
else:
|
||||
del statestack[new_state:]
|
||||
elif new_state == '#push':
|
||||
statestack.append(statestack[-1])
|
||||
else:
|
||||
assert False, "wrong state def: %r" % new_state
|
||||
statetokens = tokendefs[statestack[-1]]
|
||||
break
|
||||
else:
|
||||
# We are here only if all state tokens have been considered
|
||||
# and there was not a match on any of them.
|
||||
try:
|
||||
if text[pos] == '\n':
|
||||
# at EOL, reset state to "root"
|
||||
statestack = ['root']
|
||||
statetokens = tokendefs['root']
|
||||
yield pos, Whitespace, '\n'
|
||||
pos += 1
|
||||
continue
|
||||
yield pos, Error, text[pos]
|
||||
pos += 1
|
||||
except IndexError:
|
||||
break
|
||||
|
||||
|
||||
class LexerContext:
|
||||
"""
|
||||
A helper object that holds lexer position data.
|
||||
"""
|
||||
|
||||
def __init__(self, text, pos, stack=None, end=None):
|
||||
self.text = text
|
||||
self.pos = pos
|
||||
self.end = end or len(text) # end=0 not supported ;-)
|
||||
self.stack = stack or ['root']
|
||||
|
||||
def __repr__(self):
|
||||
return 'LexerContext(%r, %r, %r)' % (
|
||||
self.text, self.pos, self.stack)
|
||||
|
||||
|
||||
class ExtendedRegexLexer(RegexLexer):
|
||||
"""
|
||||
A RegexLexer that uses a context object to store its state.
|
||||
"""
|
||||
|
||||
def get_tokens_unprocessed(self, text=None, context=None):
|
||||
"""
|
||||
Split ``text`` into (tokentype, text) pairs.
|
||||
If ``context`` is given, use this lexer context instead.
|
||||
"""
|
||||
tokendefs = self._tokens
|
||||
if not context:
|
||||
ctx = LexerContext(text, 0)
|
||||
statetokens = tokendefs['root']
|
||||
else:
|
||||
ctx = context
|
||||
statetokens = tokendefs[ctx.stack[-1]]
|
||||
text = ctx.text
|
||||
while 1:
|
||||
for rexmatch, action, new_state in statetokens:
|
||||
m = rexmatch(text, ctx.pos, ctx.end)
|
||||
if m:
|
||||
if action is not None:
|
||||
if type(action) is _TokenType:
|
||||
yield ctx.pos, action, m.group()
|
||||
ctx.pos = m.end()
|
||||
else:
|
||||
yield from action(self, m, ctx)
|
||||
if not new_state:
|
||||
# altered the state stack?
|
||||
statetokens = tokendefs[ctx.stack[-1]]
|
||||
# CAUTION: callback must set ctx.pos!
|
||||
if new_state is not None:
|
||||
# state transition
|
||||
if isinstance(new_state, tuple):
|
||||
for state in new_state:
|
||||
if state == '#pop':
|
||||
if len(ctx.stack) > 1:
|
||||
ctx.stack.pop()
|
||||
elif state == '#push':
|
||||
ctx.stack.append(ctx.stack[-1])
|
||||
else:
|
||||
ctx.stack.append(state)
|
||||
elif isinstance(new_state, int):
|
||||
# see RegexLexer for why this check is made
|
||||
if abs(new_state) >= len(ctx.stack):
|
||||
del ctx.stack[1:]
|
||||
else:
|
||||
del ctx.stack[new_state:]
|
||||
elif new_state == '#push':
|
||||
ctx.stack.append(ctx.stack[-1])
|
||||
else:
|
||||
assert False, "wrong state def: %r" % new_state
|
||||
statetokens = tokendefs[ctx.stack[-1]]
|
||||
break
|
||||
else:
|
||||
try:
|
||||
if ctx.pos >= ctx.end:
|
||||
break
|
||||
if text[ctx.pos] == '\n':
|
||||
# at EOL, reset state to "root"
|
||||
ctx.stack = ['root']
|
||||
statetokens = tokendefs['root']
|
||||
yield ctx.pos, Text, '\n'
|
||||
ctx.pos += 1
|
||||
continue
|
||||
yield ctx.pos, Error, text[ctx.pos]
|
||||
ctx.pos += 1
|
||||
except IndexError:
|
||||
break
|
||||
|
||||
|
||||
def do_insertions(insertions, tokens):
|
||||
"""
|
||||
Helper for lexers which must combine the results of several
|
||||
sublexers.
|
||||
|
||||
``insertions`` is a list of ``(index, itokens)`` pairs.
|
||||
Each ``itokens`` iterable should be inserted at position
|
||||
``index`` into the token stream given by the ``tokens``
|
||||
argument.
|
||||
|
||||
The result is a combined token stream.
|
||||
|
||||
TODO: clean up the code here.
|
||||
"""
|
||||
insertions = iter(insertions)
|
||||
try:
|
||||
index, itokens = next(insertions)
|
||||
except StopIteration:
|
||||
# no insertions
|
||||
yield from tokens
|
||||
return
|
||||
|
||||
realpos = None
|
||||
insleft = True
|
||||
|
||||
# iterate over the token stream where we want to insert
|
||||
# the tokens from the insertion list.
|
||||
for i, t, v in tokens:
|
||||
# first iteration. store the position of first item
|
||||
if realpos is None:
|
||||
realpos = i
|
||||
oldi = 0
|
||||
while insleft and i + len(v) >= index:
|
||||
tmpval = v[oldi:index - i]
|
||||
if tmpval:
|
||||
yield realpos, t, tmpval
|
||||
realpos += len(tmpval)
|
||||
for it_index, it_token, it_value in itokens:
|
||||
yield realpos, it_token, it_value
|
||||
realpos += len(it_value)
|
||||
oldi = index - i
|
||||
try:
|
||||
index, itokens = next(insertions)
|
||||
except StopIteration:
|
||||
insleft = False
|
||||
break # not strictly necessary
|
||||
if oldi < len(v):
|
||||
yield realpos, t, v[oldi:]
|
||||
realpos += len(v) - oldi
|
||||
|
||||
# leftover tokens
|
||||
while insleft:
|
||||
# no normal tokens, set realpos to zero
|
||||
realpos = realpos or 0
|
||||
for p, t, v in itokens:
|
||||
yield realpos, t, v
|
||||
realpos += len(v)
|
||||
try:
|
||||
index, itokens = next(insertions)
|
||||
except StopIteration:
|
||||
insleft = False
|
||||
break # not strictly necessary
|
||||
|
||||
|
||||
class ProfilingRegexLexerMeta(RegexLexerMeta):
|
||||
"""Metaclass for ProfilingRegexLexer, collects regex timing info."""
|
||||
|
||||
def _process_regex(cls, regex, rflags, state):
|
||||
if isinstance(regex, words):
|
||||
rex = regex_opt(regex.words, prefix=regex.prefix,
|
||||
suffix=regex.suffix)
|
||||
else:
|
||||
rex = regex
|
||||
compiled = re.compile(rex, rflags)
|
||||
|
||||
def match_func(text, pos, endpos=sys.maxsize):
|
||||
info = cls._prof_data[-1].setdefault((state, rex), [0, 0.0])
|
||||
t0 = time.time()
|
||||
res = compiled.match(text, pos, endpos)
|
||||
t1 = time.time()
|
||||
info[0] += 1
|
||||
info[1] += t1 - t0
|
||||
return res
|
||||
return match_func
|
||||
|
||||
|
||||
class ProfilingRegexLexer(RegexLexer, metaclass=ProfilingRegexLexerMeta):
|
||||
"""Drop-in replacement for RegexLexer that does profiling of its regexes."""
|
||||
|
||||
_prof_data = []
|
||||
_prof_sort_index = 4 # defaults to time per call
|
||||
|
||||
def get_tokens_unprocessed(self, text, stack=('root',)):
|
||||
# this needs to be a stack, since using(this) will produce nested calls
|
||||
self.__class__._prof_data.append({})
|
||||
yield from RegexLexer.get_tokens_unprocessed(self, text, stack)
|
||||
rawdata = self.__class__._prof_data.pop()
|
||||
data = sorted(((s, repr(r).strip('u\'').replace('\\\\', '\\')[:65],
|
||||
n, 1000 * t, 1000 * t / n)
|
||||
for ((s, r), (n, t)) in rawdata.items()),
|
||||
key=lambda x: x[self._prof_sort_index],
|
||||
reverse=True)
|
||||
sum_total = sum(x[3] for x in data)
|
||||
|
||||
print()
|
||||
print('Profiling result for %s lexing %d chars in %.3f ms' %
|
||||
(self.__class__.__name__, len(text), sum_total))
|
||||
print('=' * 110)
|
||||
print('%-20s %-64s ncalls tottime percall' % ('state', 'regex'))
|
||||
print('-' * 110)
|
||||
for d in data:
|
||||
print('%-20s %-65s %5d %8.4f %8.4f' % d)
|
||||
print('=' * 110)
|
||||
@@ -0,0 +1,362 @@
|
||||
"""
|
||||
pygments.lexers
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Pygments lexers.
|
||||
|
||||
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
import fnmatch
|
||||
from os.path import basename
|
||||
|
||||
from pip._vendor.pygments.lexers._mapping import LEXERS
|
||||
from pip._vendor.pygments.modeline import get_filetype_from_buffer
|
||||
from pip._vendor.pygments.plugin import find_plugin_lexers
|
||||
from pip._vendor.pygments.util import ClassNotFound, guess_decode
|
||||
|
||||
COMPAT = {
|
||||
'Python3Lexer': 'PythonLexer',
|
||||
'Python3TracebackLexer': 'PythonTracebackLexer',
|
||||
}
|
||||
|
||||
__all__ = ['get_lexer_by_name', 'get_lexer_for_filename', 'find_lexer_class',
|
||||
'guess_lexer', 'load_lexer_from_file'] + list(LEXERS) + list(COMPAT)
|
||||
|
||||
_lexer_cache = {}
|
||||
_pattern_cache = {}
|
||||
|
||||
|
||||
def _fn_matches(fn, glob):
|
||||
"""Return whether the supplied file name fn matches pattern filename."""
|
||||
if glob not in _pattern_cache:
|
||||
pattern = _pattern_cache[glob] = re.compile(fnmatch.translate(glob))
|
||||
return pattern.match(fn)
|
||||
return _pattern_cache[glob].match(fn)
|
||||
|
||||
|
||||
def _load_lexers(module_name):
|
||||
"""Load a lexer (and all others in the module too)."""
|
||||
mod = __import__(module_name, None, None, ['__all__'])
|
||||
for lexer_name in mod.__all__:
|
||||
cls = getattr(mod, lexer_name)
|
||||
_lexer_cache[cls.name] = cls
|
||||
|
||||
|
||||
def get_all_lexers(plugins=True):
|
||||
"""Return a generator of tuples in the form ``(name, aliases,
|
||||
filenames, mimetypes)`` of all know lexers.
|
||||
|
||||
If *plugins* is true (the default), plugin lexers supplied by entrypoints
|
||||
are also returned. Otherwise, only builtin ones are considered.
|
||||
"""
|
||||
for item in LEXERS.values():
|
||||
yield item[1:]
|
||||
if plugins:
|
||||
for lexer in find_plugin_lexers():
|
||||
yield lexer.name, lexer.aliases, lexer.filenames, lexer.mimetypes
|
||||
|
||||
|
||||
def find_lexer_class(name):
|
||||
"""
|
||||
Return the `Lexer` subclass that with the *name* attribute as given by
|
||||
the *name* argument.
|
||||
"""
|
||||
if name in _lexer_cache:
|
||||
return _lexer_cache[name]
|
||||
# lookup builtin lexers
|
||||
for module_name, lname, aliases, _, _ in LEXERS.values():
|
||||
if name == lname:
|
||||
_load_lexers(module_name)
|
||||
return _lexer_cache[name]
|
||||
# continue with lexers from setuptools entrypoints
|
||||
for cls in find_plugin_lexers():
|
||||
if cls.name == name:
|
||||
return cls
|
||||
|
||||
|
||||
def find_lexer_class_by_name(_alias):
|
||||
"""
|
||||
Return the `Lexer` subclass that has `alias` in its aliases list, without
|
||||
instantiating it.
|
||||
|
||||
Like `get_lexer_by_name`, but does not instantiate the class.
|
||||
|
||||
Will raise :exc:`pygments.util.ClassNotFound` if no lexer with that alias is
|
||||
found.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
"""
|
||||
if not _alias:
|
||||
raise ClassNotFound('no lexer for alias %r found' % _alias)
|
||||
# lookup builtin lexers
|
||||
for module_name, name, aliases, _, _ in LEXERS.values():
|
||||
if _alias.lower() in aliases:
|
||||
if name not in _lexer_cache:
|
||||
_load_lexers(module_name)
|
||||
return _lexer_cache[name]
|
||||
# continue with lexers from setuptools entrypoints
|
||||
for cls in find_plugin_lexers():
|
||||
if _alias.lower() in cls.aliases:
|
||||
return cls
|
||||
raise ClassNotFound('no lexer for alias %r found' % _alias)
|
||||
|
||||
|
||||
def get_lexer_by_name(_alias, **options):
|
||||
"""
|
||||
Return an instance of a `Lexer` subclass that has `alias` in its
|
||||
aliases list. The lexer is given the `options` at its
|
||||
instantiation.
|
||||
|
||||
Will raise :exc:`pygments.util.ClassNotFound` if no lexer with that alias is
|
||||
found.
|
||||
"""
|
||||
if not _alias:
|
||||
raise ClassNotFound('no lexer for alias %r found' % _alias)
|
||||
|
||||
# lookup builtin lexers
|
||||
for module_name, name, aliases, _, _ in LEXERS.values():
|
||||
if _alias.lower() in aliases:
|
||||
if name not in _lexer_cache:
|
||||
_load_lexers(module_name)
|
||||
return _lexer_cache[name](**options)
|
||||
# continue with lexers from setuptools entrypoints
|
||||
for cls in find_plugin_lexers():
|
||||
if _alias.lower() in cls.aliases:
|
||||
return cls(**options)
|
||||
raise ClassNotFound('no lexer for alias %r found' % _alias)
|
||||
|
||||
|
||||
def load_lexer_from_file(filename, lexername="CustomLexer", **options):
|
||||
"""Load a lexer from a file.
|
||||
|
||||
This method expects a file located relative to the current working
|
||||
directory, which contains a Lexer class. By default, it expects the
|
||||
Lexer to be name CustomLexer; you can specify your own class name
|
||||
as the second argument to this function.
|
||||
|
||||
Users should be very careful with the input, because this method
|
||||
is equivalent to running eval on the input file.
|
||||
|
||||
Raises ClassNotFound if there are any problems importing the Lexer.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
"""
|
||||
try:
|
||||
# This empty dict will contain the namespace for the exec'd file
|
||||
custom_namespace = {}
|
||||
with open(filename, 'rb') as f:
|
||||
exec(f.read(), custom_namespace)
|
||||
# Retrieve the class `lexername` from that namespace
|
||||
if lexername not in custom_namespace:
|
||||
raise ClassNotFound('no valid %s class found in %s' %
|
||||
(lexername, filename))
|
||||
lexer_class = custom_namespace[lexername]
|
||||
# And finally instantiate it with the options
|
||||
return lexer_class(**options)
|
||||
except OSError as err:
|
||||
raise ClassNotFound('cannot read %s: %s' % (filename, err))
|
||||
except ClassNotFound:
|
||||
raise
|
||||
except Exception as err:
|
||||
raise ClassNotFound('error when loading custom lexer: %s' % err)
|
||||
|
||||
|
||||
def find_lexer_class_for_filename(_fn, code=None):
|
||||
"""Get a lexer for a filename.
|
||||
|
||||
If multiple lexers match the filename pattern, use ``analyse_text()`` to
|
||||
figure out which one is more appropriate.
|
||||
|
||||
Returns None if not found.
|
||||
"""
|
||||
matches = []
|
||||
fn = basename(_fn)
|
||||
for modname, name, _, filenames, _ in LEXERS.values():
|
||||
for filename in filenames:
|
||||
if _fn_matches(fn, filename):
|
||||
if name not in _lexer_cache:
|
||||
_load_lexers(modname)
|
||||
matches.append((_lexer_cache[name], filename))
|
||||
for cls in find_plugin_lexers():
|
||||
for filename in cls.filenames:
|
||||
if _fn_matches(fn, filename):
|
||||
matches.append((cls, filename))
|
||||
|
||||
if isinstance(code, bytes):
|
||||
# decode it, since all analyse_text functions expect unicode
|
||||
code = guess_decode(code)
|
||||
|
||||
def get_rating(info):
|
||||
cls, filename = info
|
||||
# explicit patterns get a bonus
|
||||
bonus = '*' not in filename and 0.5 or 0
|
||||
# The class _always_ defines analyse_text because it's included in
|
||||
# the Lexer class. The default implementation returns None which
|
||||
# gets turned into 0.0. Run scripts/detect_missing_analyse_text.py
|
||||
# to find lexers which need it overridden.
|
||||
if code:
|
||||
return cls.analyse_text(code) + bonus, cls.__name__
|
||||
return cls.priority + bonus, cls.__name__
|
||||
|
||||
if matches:
|
||||
matches.sort(key=get_rating)
|
||||
# print "Possible lexers, after sort:", matches
|
||||
return matches[-1][0]
|
||||
|
||||
|
||||
def get_lexer_for_filename(_fn, code=None, **options):
|
||||
"""Get a lexer for a filename.
|
||||
|
||||
Return a `Lexer` subclass instance that has a filename pattern
|
||||
matching `fn`. The lexer is given the `options` at its
|
||||
instantiation.
|
||||
|
||||
Raise :exc:`pygments.util.ClassNotFound` if no lexer for that filename
|
||||
is found.
|
||||
|
||||
If multiple lexers match the filename pattern, use their ``analyse_text()``
|
||||
methods to figure out which one is more appropriate.
|
||||
"""
|
||||
res = find_lexer_class_for_filename(_fn, code)
|
||||
if not res:
|
||||
raise ClassNotFound('no lexer for filename %r found' % _fn)
|
||||
return res(**options)
|
||||
|
||||
|
||||
def get_lexer_for_mimetype(_mime, **options):
|
||||
"""
|
||||
Return a `Lexer` subclass instance that has `mime` in its mimetype
|
||||
list. The lexer is given the `options` at its instantiation.
|
||||
|
||||
Will raise :exc:`pygments.util.ClassNotFound` if not lexer for that mimetype
|
||||
is found.
|
||||
"""
|
||||
for modname, name, _, _, mimetypes in LEXERS.values():
|
||||
if _mime in mimetypes:
|
||||
if name not in _lexer_cache:
|
||||
_load_lexers(modname)
|
||||
return _lexer_cache[name](**options)
|
||||
for cls in find_plugin_lexers():
|
||||
if _mime in cls.mimetypes:
|
||||
return cls(**options)
|
||||
raise ClassNotFound('no lexer for mimetype %r found' % _mime)
|
||||
|
||||
|
||||
def _iter_lexerclasses(plugins=True):
|
||||
"""Return an iterator over all lexer classes."""
|
||||
for key in sorted(LEXERS):
|
||||
module_name, name = LEXERS[key][:2]
|
||||
if name not in _lexer_cache:
|
||||
_load_lexers(module_name)
|
||||
yield _lexer_cache[name]
|
||||
if plugins:
|
||||
yield from find_plugin_lexers()
|
||||
|
||||
|
||||
def guess_lexer_for_filename(_fn, _text, **options):
|
||||
"""
|
||||
As :func:`guess_lexer()`, but only lexers which have a pattern in `filenames`
|
||||
or `alias_filenames` that matches `filename` are taken into consideration.
|
||||
|
||||
:exc:`pygments.util.ClassNotFound` is raised if no lexer thinks it can
|
||||
handle the content.
|
||||
"""
|
||||
fn = basename(_fn)
|
||||
primary = {}
|
||||
matching_lexers = set()
|
||||
for lexer in _iter_lexerclasses():
|
||||
for filename in lexer.filenames:
|
||||
if _fn_matches(fn, filename):
|
||||
matching_lexers.add(lexer)
|
||||
primary[lexer] = True
|
||||
for filename in lexer.alias_filenames:
|
||||
if _fn_matches(fn, filename):
|
||||
matching_lexers.add(lexer)
|
||||
primary[lexer] = False
|
||||
if not matching_lexers:
|
||||
raise ClassNotFound('no lexer for filename %r found' % fn)
|
||||
if len(matching_lexers) == 1:
|
||||
return matching_lexers.pop()(**options)
|
||||
result = []
|
||||
for lexer in matching_lexers:
|
||||
rv = lexer.analyse_text(_text)
|
||||
if rv == 1.0:
|
||||
return lexer(**options)
|
||||
result.append((rv, lexer))
|
||||
|
||||
def type_sort(t):
|
||||
# sort by:
|
||||
# - analyse score
|
||||
# - is primary filename pattern?
|
||||
# - priority
|
||||
# - last resort: class name
|
||||
return (t[0], primary[t[1]], t[1].priority, t[1].__name__)
|
||||
result.sort(key=type_sort)
|
||||
|
||||
return result[-1][1](**options)
|
||||
|
||||
|
||||
def guess_lexer(_text, **options):
|
||||
"""
|
||||
Return a `Lexer` subclass instance that's guessed from the text in
|
||||
`text`. For that, the :meth:`.analyse_text()` method of every known lexer
|
||||
class is called with the text as argument, and the lexer which returned the
|
||||
highest value will be instantiated and returned.
|
||||
|
||||
:exc:`pygments.util.ClassNotFound` is raised if no lexer thinks it can
|
||||
handle the content.
|
||||
"""
|
||||
|
||||
if not isinstance(_text, str):
|
||||
inencoding = options.get('inencoding', options.get('encoding'))
|
||||
if inencoding:
|
||||
_text = _text.decode(inencoding or 'utf8')
|
||||
else:
|
||||
_text, _ = guess_decode(_text)
|
||||
|
||||
# try to get a vim modeline first
|
||||
ft = get_filetype_from_buffer(_text)
|
||||
|
||||
if ft is not None:
|
||||
try:
|
||||
return get_lexer_by_name(ft, **options)
|
||||
except ClassNotFound:
|
||||
pass
|
||||
|
||||
best_lexer = [0.0, None]
|
||||
for lexer in _iter_lexerclasses():
|
||||
rv = lexer.analyse_text(_text)
|
||||
if rv == 1.0:
|
||||
return lexer(**options)
|
||||
if rv > best_lexer[0]:
|
||||
best_lexer[:] = (rv, lexer)
|
||||
if not best_lexer[0] or best_lexer[1] is None:
|
||||
raise ClassNotFound('no lexer matching the text found')
|
||||
return best_lexer[1](**options)
|
||||
|
||||
|
||||
class _automodule(types.ModuleType):
|
||||
"""Automatically import lexers."""
|
||||
|
||||
def __getattr__(self, name):
|
||||
info = LEXERS.get(name)
|
||||
if info:
|
||||
_load_lexers(info[0])
|
||||
cls = _lexer_cache[info[1]]
|
||||
setattr(self, name, cls)
|
||||
return cls
|
||||
if name in COMPAT:
|
||||
return getattr(self, COMPAT[name])
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
oldmod = sys.modules[__name__]
|
||||
newmod = _automodule(__name__)
|
||||
newmod.__dict__.update(oldmod.__dict__)
|
||||
sys.modules[__name__] = newmod
|
||||
del newmod.newmod, newmod.oldmod, newmod.sys, newmod.types
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user