Como você implementar patternsets de estilo formiga em python para selecionar grupos de arquivos?
Pergunta
Ant tem uma boa maneira de selecionar grupos de arquivos, a maioria com folga utilizando ** para indicar uma árvore de diretórios. Por exemplo.
**/CVS/* # All files immediately under a CVS directory.
mydir/mysubdir/** # All files recursively under mysubdir
Mais exemplos podem ser vistos aqui:
http://ant.apache.org/manual/dirtasks.html
Como você implementar isso em python, de modo que você poderia fazer algo como:
files = get_files("**/CVS/*")
for file in files:
print file
=>
CVS/Repository
mydir/mysubdir/CVS/Entries
mydir/mysubdir/foo/bar/CVS/Entries
Solução
Assim que você se deparar com um **
, você vai ter que recurse através de toda a estrutura de diretórios, então eu acho que neste ponto, o método mais fácil é para percorrer o diretório com os.walk, construir um caminho e, em seguida, verificar se ele corresponde ao padrão. Provavelmente, você pode converter para uma regex por algo como:
def glob_to_regex(pat, dirsep=os.sep):
dirsep = re.escape(dirsep)
print re.escape(pat)
regex = (re.escape(pat).replace("\\*\\*"+dirsep,".*")
.replace("\\*\\*",".*")
.replace("\\*","[^%s]*" % dirsep)
.replace("\\?","[^%s]" % dirsep))
return re.compile(regex+"$")
(Embora note que este não é que com todos os recursos - que não suporta padrões glob estilo [a-z]
por exemplo, embora isso provavelmente poderia ser adicionado). (A primeira partida \*\*/
é cobrir casos como \*\*/CVS
./CVS
de correspondência, bem como ter apenas \*\*
para corresponder na cauda.)
No entanto, obviamente, você não quer recurse através de tudo abaixo da pasta corrente quando não estiver processando um padrão **
, então eu acho que você vai precisar de uma abordagem em duas fases. Eu não tentei implementar a seguir, e há provavelmente alguns casos de canto, mas eu acho que deve funcionar:
-
Dividir o padrão em seu seperator diretório. ie
pat.split('/') -> ['**','CVS','*']
-
Recurse através dos diretórios, e olhar para a parte relevante do padrão para este nível. ie.
n levels deep -> look at pat[n]
. -
Se a chave
pat[n] == '**'
à estratégia acima:- reconstruir o padrão com
dirsep.join(pat[n:])
- converter para um regex com
glob\_to\_regex()
- Recursively
os.walk
através do diretório atual, construindo o caminho relativo para o nível que começou no. Se o caminho corresponde a regex, cedê-lo.
- reconstruir o padrão com
-
Se pat não corresponde
"**"
, e é o último elemento no padrão, em seguida, produzir todos os arquivos / diretórios correspondentesglob.glob(os.path.join(curpath,pat[n]))
-
Se pat não corresponde
"**"
, e não é o último elemento no padrão, em seguida, para cada diretório, verifique se ele corresponde (com glob)pat[n]
. Se assim for, recurse para baixo através dele, incrementar a profundidade (por isso vai olhar parapat[n+1]
)
Outras dicas
Infelizmente, este é um bom tempo após a sua OP. I acabou de lançar um pacote Python que faz exatamente isso - ele é chamado fórmico e está disponível na PyPI Cheeseshop . Com fórmico, o seu problema é resolvido com:
import formic
fileset = formic.FileSet(include="**/CVS/*", default_excludes=False)
for file_name in fileset.qualified_files():
print file_name
Há um pequeno complexidade: default_excludes. Fórmico, assim como Ant, diretórios exclui CVS por padrão (como a maior parte coletar arquivos a partir deles para uma compilação é perigoso), a resposta padrão para a pergunta resultaria em nenhum arquivo. Definindo default_excludes = desativa falsos esse comportamento.
os.walk
é seu amigo. Veja o exemplo no manual Python
( https://docs.python.org/2/library/os .html # os.walk ) e tentar construir algo a partir disso.
Para corresponder "**/CVS/*
" contra um nome de arquivo que você começa, você pode fazer algo como isto:
def match(pattern, filename):
if pattern.startswith("**"):
return fnmatch.fnmatch(file, pattern[1:])
else:
return fnmatch.fnmatch(file, pattern)
Em fnmatch.fnmatch
, "*" corresponde a qualquer coisa (incluindo barras).
Há uma implementação no código fonte do sistema build 'waf'. http://code.google .com / p / WAF / fonte / browse / trunk / waflib / Node.py? r = 10755 # 471 Pode ser este deve ser embrulhado em uma biblioteca própria?
os.walk é sua melhor aposta por esta. Eu fiz o exemplo abaixo com .svn porque eu tinha que prático, e funcionou muito bem:
import re
for (dirpath, dirnames, filenames) in os.walk("."):
if re.search(r'\.svn$', dirpath):
for file in filenames:
print file