2 Various utility functions used by gitology.*
5 from django
.conf
.urls
.defaults
import patterns
6 from django
.utils
import simplejson
8 import sys
, os
, textwrap
13 from gitology
import path
15 import fnmatch
, re
, path
, string
17 import docutils
.writers
.html4css1
, docutils
.core
18 from odict
import OrderedDict
as odict
21 class ImproperlyConfigured(Exception): pass
25 from django
.core
.urlresolvers
import get_mod_func
26 mod_name
, obj_name
= get_mod_func(path
)
27 return getattr(__import__(mod_name
, {}, {}, ['']), obj_name
)
30 def text_to_html(text_input
): return "<pre>%s</pre>" % text_input
33 def rest_to_html(rest_input
, css_path
=None):
34 """Render ReStructuredText."""
35 writer
= docutils
.writers
.html4css1
.Writer()
36 from gitology
.config
import settings
38 if css_path
is None and "DEFAULT_REST_CSS" in settings
.DEFAULTS
:
39 css_path
= os
.path
.expanduser(settings
.DEFAULTS
.DEFAULT_REST_CSS
)
40 return docutils
.core
.publish_parts(
41 rest_input
, writer_name
="html", settings_overrides
={
42 'stylesheet': css_path
,
43 'stylesheet_path': None,
44 'embed_stylesheet': True
51 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/361668
53 """A dict whose items can also be accessed as member variables.
55 >>> d = attrdict(a=1, b=2)
57 >>> print d.a, d.b, d.c
63 # but be careful, it's easy to hide methods
68 Traceback (most recent call last):
69 TypeError: 'int' object is not callable
71 def __init__(self
, *args
, **kwargs
):
72 dict.__init
__(self
, *args
, **kwargs
)
77 class NamedObject(object):
78 def __init__(self
, name
):
80 def _get_name(self
): return self
._name
83 self.name is readonly.
85 >>> d = DocumentMeta("somename")
88 >>> d.name = "new name"
89 Traceback (most recent call last):
91 AttributeError: can't set attribute
95 def __unicode__(self
):
96 return u
"%s(%s)" % ( self
.__class
__.__name
__, self
.name
)
98 __repr__
= __unicode__
101 # get_blog_data # {{{
102 class list_with_clone(list):
103 def _clone(self
): return self
105 global_blog_dict
= {}
106 def get_blog_data(p
):
107 from gitology
.document
import Document
109 blog
["name"] = p
.basename()
110 blog
["document"] = Document("blogs@%s" % blog
["name"])
111 if p
.basename() == "main": blog
["prefix"] = "blog/"
112 else: blog
["prefix"] = "%s/" % p
.basename()
114 blog
["posts"] = odict()
118 if y
.namebase
== "labels": continue
122 dates
= m
.glob("*.lst")
125 for l
in d
.open().readlines():
126 # format: url document_name timestamp
127 url
, document_name
, timestamp
= l
.split(" ", 2)
128 blog
["posts"][url
] = {
129 'date': timestamp
, 'document': Document(document_name
),
131 global_blog_dict
[url
] = blog
132 blog
["posts"].reverse()
135 for l
in p
.joinpath("labels").glob("*.lst"):
137 d
["name"] = l
.namebase
138 d
["posts"] = list_with_clone()
139 d
["document"] = Document("blogs@%s@label@%s" % (blog
["name"], l
.namebase
))
140 for line
in l
.open().readlines():
141 # format: url, data is in respective archive file
142 d
["posts"].append(blog
["posts"][line
.strip()])
143 blog
["posts"][line
.strip()].setdefault("labels", []).append(d
)
144 blog
["labels"][l
.namebase
] = d
153 ("^%s$" % b
["prefix"], "gitology.d.views.show_blog", { 'blog_data': b
,})
157 "^%slabelled/(?P<label_name>[^/]+)/$" % b
["prefix"],
158 "gitology.d.views.show_category", { 'blog_data': b
},
161 from django
.contrib
.syndication
.feeds
import Feed
162 class LatestEntries(Feed
):
163 title
= b
["document"].meta
.title
164 link
= b
["document"].meta
.title
165 description
= b
["document"].meta
.subtitle
168 return b
["posts"].values()[:10]
170 def item_link(self
, item
):
171 return item
["document"].meta
.url
173 feeds
= { 'latest': LatestEntries
}
177 '^%sfeeds/(?P<url>.*)/$' % b
["prefix"],
178 'django.contrib.syndication.views.feed',
179 {'feed_dict': feeds
},
187 from gitology
.config
import settings
as gsettings
189 blogs_folder
= gsettings
.LOCAL_REPO_PATH
.joinpath("blogs")
190 for d
in blogs_folder
.dirs():
196 global_wiki_dict
= {}
198 from gitology
.config
import settings
as gsettings
199 from gitology
.document
import Document
201 wiki_folder
= gsettings
.LOCAL_REPO_PATH
.joinpath("wiki")
202 for i
in wiki_folder
.walk():
203 if not i
.isfile(): continue
204 wiki_document
= Document(i
.open().read().strip())
205 wiki_url
= i
[len(wiki_folder
):-4] + "/"
206 global_wiki_dict
[wiki_url
] = wiki_document
210 # get redirects # {{{
211 global_redirect_dict
= {}
213 from gitology
.config
import settings
as gsettings
214 redirects_file
= gsettings
.LOCAL_REPO_PATH
.joinpath("redirects.lst")
215 if not redirects_file
.exists(): return
216 for line
in redirects_file
.open().readlines():
218 before
, after
= line
.split()
220 print "Bad redirect line: ", line
222 global_redirect_dict
[before
] = after
225 # refresh_urlconf_cache # {{{
226 def refresh_urlconf_cache():
227 print "refresh_urlconf_cache"
228 from gitology
.config
import settings
229 """ creates a urlconf that is stored """
230 global global_wiki_dict
, global_blog_dict
, global_redirect_dict
231 global_blog_dict
= {}
232 global_wiki_dict
= {}
233 global_redirect_dict
= {}
236 # list of blogs is in $reporoot/blogs/
238 # blog named "main" goes under /blog/, rest of them go to /folder_name/
242 # for each blog, list of labels in $reporoot/blogs/blog_name/labels/
243 # urls: /blog_name/label/label_name/
244 # for each blog, date based heirarchy is kept in
245 # $reporoot/blogs/blog_name/year/month/date.lst
246 # /blog_name/year/month/date/document_name/
249 # list of wiki document names are in $reporoot/wiki/document_alias.txt
250 # urls: /document_alias/
251 # further heirarchy is maintained:
252 # $reporoot/wiki/document_alias/child_alias.txt
253 # /document_alias/child_alias/
256 # notebooks are stored in $reporoot/notebooks/
257 # urls: /notebook/ this is a dedicated app
260 # list of albums are in $reporoot/albums/ album_name.meta, album_name.lst
261 # urls: /album|gallery/album_name/ this is document. it can contain select
263 # each photo is basically a document, its list is in the album_name.lst
264 # each album photo ll have a thumbnail and caption meta data to be shown
266 # /album/album_name/photos/ will list all photos, each photo may be in one
267 # or more albums. each photo can be a blog post too in one or more blogs
269 # optimization: this info will be loaded from a file, and some other tool
270 # is to update this file everytime something interesting happens.
272 # this function returns a dict containing url to view mapping.
273 file(settings
.LOCAL_REPO_PATH
.joinpath("urlconf.cache"), "w+").write(
274 #simplejson.dumps(urls)
282 # http://nedbatchelder.com/blog/200712/human_sorting.html
289 """ Turn a string into a list of string and number chunks.
290 "z23a" -> ["z", 23, "a"]
292 return [ tryint(c
) for c
in re
.split('([0-9]+)', s
) ]
295 """ Sort the given list in the way that humans expect.
297 l
.sort(key
=alphanum_key
)
301 def smart_wrap(s
, width
=None):
303 width
= int(os
.environ
.get("COLUMNS", 70))
305 for line
in s
.split("\n"):
306 if not line
.split(): lines
.append("")
307 else: lines
+= textwrap
.wrap(line
, width
=width
)
308 return "\n".join(lines
)
312 # stolen from: http://mail.python.org/pipermail/python-list/2000-June/037460.html
313 def calcDirSize(arg
, dir, files
):
315 stats
= os
.stat(os
.path
.join(dir, file))
321 os
.path
.walk(dir, calcDirSize
, sizes
)
325 if total
> 1073741824:
326 return (round(total
/1073741824.0, 2), 'GB')
328 return (round(total
/1048576.0, 2), 'MB')
330 return (round(total
/1024.0, 2), 'KB')
331 return (total
, 'bytes')
334 # generators from http://www.dabeaz.com/generators/ # {{{
335 # these generators are one time use only
337 # use http://www.fiber-space.de/generator_tools/doc/generator_tools.html
338 # for copying generators.
340 def gen_find(filepat
,top
=""):
341 if not top
: top
= path
.path(".").abspath()
342 top
= os
.path
.expanduser(top
)
343 for path_
, dirlist
, filelist
in os
.walk(top
):
344 for name
in fnmatch
.filter(filelist
, filepat
):
345 yield os
.path
.join(path_
, name
)
347 def gen_exclude(pattern
, lines
):
348 patter
= re
.compile(pattern
)
350 if not re
.search(pattern
, line
):
353 def gen_grep(pattern
, lines
):
354 patter
= re
.compile(pattern
)
356 if re
.search(pattern
, line
):
359 def gen_open(filenames
):
360 for name
in filenames
:
361 if name
.endswith(".gz"):
362 yield gzip
.open(name
)
363 elif name
.endswith(".bz2"):
364 yield bz2
.BZ2File(name
)
368 def gen_cat(sources
):
375 for item
in gen
: c
+= 1
380 allowed_chars_in_urls
= string
.ascii_letters
+ string
.digits
+ ".-_/"
381 def is_valid_url(url
):
382 if not url
.startswith("/"): return False
384 if c
not in allowed_chars_in_urls
:
386 # consecutive // not allowed
387 for p
in url
.split("/")[1:-1]:
388 if not p
: return False
394 from django
.db
.models
.fields
import DateTimeField
395 dtf
= DateTimeField()
396 return dtf
.to_python(s
)