├── .gitignore ├── README.md ├── app ├── allpages.py ├── config.py ├── history.py ├── main.py ├── profiling.py ├── server.py ├── sites.py ├── static │ ├── app.css │ ├── app.js │ ├── bootstrap.min.css │ ├── catwiki.ico │ ├── font-awesome-4.7.0 │ │ ├── HELP-US-OUT.txt │ │ ├── css │ │ │ ├── font-awesome.css │ │ │ └── font-awesome.min.css │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── less │ │ │ ├── animated.less │ │ │ ├── bordered-pulled.less │ │ │ ├── core.less │ │ │ ├── fixed-width.less │ │ │ ├── font-awesome.less │ │ │ ├── icons.less │ │ │ ├── larger.less │ │ │ ├── list.less │ │ │ ├── mixins.less │ │ │ ├── path.less │ │ │ ├── rotated-flipped.less │ │ │ ├── screen-reader.less │ │ │ ├── stacked.less │ │ │ └── variables.less │ │ └── scss │ │ │ ├── _animated.scss │ │ │ ├── _bordered-pulled.scss │ │ │ ├── _core.scss │ │ │ ├── _fixed-width.scss │ │ │ ├── _icons.scss │ │ │ ├── _larger.scss │ │ │ ├── _list.scss │ │ │ ├── _mixins.scss │ │ │ ├── _path.scss │ │ │ ├── _rotated-flipped.scss │ │ │ ├── _screen-reader.scss │ │ │ ├── _stacked.scss │ │ │ ├── _variables.scss │ │ │ └── font-awesome.scss │ ├── fonts │ │ ├── LICENSE.txt │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-BoldItalic.ttf │ │ ├── Roboto-Italic.ttf │ │ ├── Roboto-Regular.ttf │ │ ├── UFL.txt │ │ ├── UbuntuMono-Bold.ttf │ │ ├── UbuntuMono-BoldItalic.ttf │ │ ├── UbuntuMono-Italic.ttf │ │ ├── UbuntuMono-Regular.ttf │ │ ├── icomoon.eot │ │ ├── icomoon.svg │ │ ├── icomoon.ttf │ │ └── icomoon.woff │ ├── jquery-3.1.1.min.js │ ├── jquery.selection.js │ ├── jsfunctions.js │ └── pygments.css ├── templates │ ├── front_page.html │ ├── generic_10_2.html │ ├── help.html │ ├── history.html │ ├── main.html │ ├── user.html │ ├── users.html │ ├── wiki_index.html │ ├── wiki_page.html │ └── wikiedit.html ├── ulib │ ├── HISTORY │ ├── __init__.py │ ├── butil.py │ ├── debugdec.py │ ├── istream.py │ ├── lintest.py │ └── termcolours.py └── wiki.py ├── data └── catwiki │ ├── CatWiki enhancements to Markdown.md │ ├── Credits.md │ ├── Editing toolbar.md │ ├── Editing.md │ ├── Installing CatWiki.md │ ├── Markdown.md │ ├── article.png │ ├── article_editor.png │ ├── development │ ├── MeowCat Cat Icons.md │ ├── Short-term to-do list.md │ ├── home.md │ ├── long term development plan.md │ ├── redirection.md │ ├── todo.md │ └── versioning.md │ ├── faq.md │ ├── folder_view.png │ ├── help.md │ ├── home.md │ ├── meowcat │ ├── Goals of MeowCat.md │ ├── Ideas for features to add.md │ ├── MeowCat Use-Cases.md │ ├── home.md │ ├── similar ideas.md │ └── similar projects.md │ ├── multiple wikis.md │ ├── sandbox.md │ ├── sandbox │ ├── abc │ │ ├── bar2.md │ │ ├── ddd │ │ │ └── ggg.md │ │ └── def │ │ │ └── ghi.md │ └── about.md │ ├── screenshots.md │ └── syntax.md ├── requirements.txt ├── runme └── startup /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore for Python projects 2 | 3 | #### ignore compiled Python modules 4 | *.pyc 5 | *.pyo 6 | 7 | #### ignore backup files made by text editor 8 | *~ 9 | 10 | #### ignore virtual environment 11 | venv/* 12 | 13 | #### ignore misc files 14 | nohup.out 15 | 16 | #end 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About CatWiki 2 | 3 | CatWiki is very simple wiki software that stores its articles as text files. 4 | 5 | ## Features 6 | 7 | * Stores articles as text files, so they are easy to back up and manipulate. Even if the wiki software goes down, you can still get at your notes. 8 | * Uses an enhanced version of the the Markdown markup language. 9 | * Allows multiple wikis per installation, each one in its own directory. 10 | * Allows subdirectories. You can navigate through the directories using the web interface. If the directory includes image files, the web page shows thumbnails of them. 11 | * You can add Font Awesome icons in your wiki pages. 12 | 13 | ## Technology Used 14 | 15 | CatWiki is written in Python and uses the Flask lightweight web framework. 16 | 17 | ## Screenshots 18 | 19 | Here are some screenshots of CatWiki in action. 20 | 21 | ### An article 22 | 23 | Here is an article in CatWiki: 24 | 25 | ![](data/catwiki/article.png) 26 | 27 | ### The article editor 28 | 29 | Here is the same article in CatWiki's text editor: 30 | 31 | ![](data/catwiki/article_editor.png) 32 | 33 | ### Folder view 34 | 35 | This shows all the articles, other files, and sub-folders in a folder. 36 | 37 | ![](data/catwiki/folder_view.png) 38 | 39 | -------------------------------------------------------------------------------- /app/allpages.py: -------------------------------------------------------------------------------- 1 | # allpages.py = stuff relevant to all pages 2 | 3 | import os.path 4 | import collections 5 | import cgi 6 | 7 | import config 8 | 9 | from flask import Flask, request 10 | app = Flask(__name__) 11 | #app.config["SECRET_KEY"] = "don't tell anyone" # not using 12 | app.config["SESSION_COOKIE_NAME"] = "session_%d" % (config.PORT,) 13 | app.config["WERKZEUG_DEBUG_PIN"] = "off" 14 | 15 | from ulib import debugdec, butil, termcolours 16 | from ulib.debugdec import printargs, prvars 17 | 18 | #--------------------------------------------------------------------- 19 | # jinja2 environment 20 | 21 | import jinja2 22 | from jinja2 import Template 23 | 24 | jinjaEnv = jinja2.Environment() 25 | thisDir = os.path.dirname(os.path.realpath(__file__)) 26 | templateDir = butil.join(thisDir, "templates") 27 | jinjaEnv.loader = jinja2.FileSystemLoader(templateDir) 28 | 29 | 30 | #--------------------------------------------------------------------- 31 | # login manager 32 | 33 | def helpPage(): 34 | p = request.path[1:] 35 | r = p.split('/')[0] 36 | if r=="": r = "main" 37 | return r 38 | jinjaEnv.globals['helpPage'] = helpPage 39 | 40 | def highlightPageIfCurrent(testUrl): 41 | """ If the current page starts with (testUrl), highlight it 42 | by returning the code " class='active'". 43 | Otherwise return "" 44 | """ 45 | p = request.path.lstrip("/") 46 | if p.startswith(testUrl): return " class='active'" 47 | return "" 48 | 49 | jinjaEnv.globals['hpic'] = highlightPageIfCurrent 50 | 51 | #--------------------------------------------------------------------- 52 | # utility functions 53 | 54 | def form(s, *args, **kwargs): 55 | return s.format(*args, **kwargs) 56 | 57 | def minToHMS(m): 58 | """ convert a time in minutes into hh:mm:ss 59 | @param m [int|float] 60 | @return [str] 61 | """ 62 | mn = m % 60 63 | hr = int(m/60.0) 64 | mn = m - hr*60 65 | mn2 = int(mn) 66 | sc = int((mn-mn2)*60) 67 | r = "{:d}:{:02d}:{:02d}".format(hr, mn2, sc) 68 | return r 69 | 70 | def htmlEscape(s): 71 | return cgi.escape(s) 72 | htmlEsc = htmlEscape 73 | 74 | #--------------------------------------------------------------------- 75 | 76 | #end 77 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | # config.py = configuration data 2 | 3 | import os.path 4 | from ulib import butil 5 | 6 | #--------------------------------------------------------------------- 7 | 8 | # port we are running on 9 | PORT=7331 10 | 11 | # A list of directories which might be followed by sites. 12 | # New sites are always created using the last one on this list. 13 | SITE_STUBS = [ 14 | butil.join(os.path.dirname(__file__), "../data"), 15 | butil.join("~/siteboxdata/sites"), 16 | ] 17 | 18 | #--------------------------------------------------------------------- 19 | 20 | #end 21 | -------------------------------------------------------------------------------- /app/history.py: -------------------------------------------------------------------------------- 1 | # history.py 2 | 3 | import os.path, datetime, time 4 | 5 | from flask import request, redirect, Response 6 | 7 | import git 8 | 9 | from ulib import butil 10 | from ulib.butil import form 11 | from ulib.debugdec import prvars, pr 12 | 13 | import allpages 14 | from allpages import * 15 | 16 | import wiki 17 | 18 | #--------------------------------------------------------------------- 19 | 20 | def getRepo(siteName, pathName): 21 | """ 22 | @param siteName::str 23 | @param pathName::str 24 | @return::Repo = a git repository 25 | """ 26 | dp = wiki.getDirPan(siteName, "") 27 | prvars("dp") 28 | repo = git.Repo(dp) 29 | prvars("dp repo") 30 | return repo 31 | 32 | #--------------------------------------------------------------------- 33 | 34 | @app.route("//history/") 35 | def history(siteName, pathName): 36 | pr("siteName=%r pathName=%r", siteName, pathName) 37 | tem = jinjaEnv.get_template("history.html") 38 | 39 | repo = getRepo(siteName, pathName) 40 | #lsf = repo.git.log(form("--follow {}.md", pathName)) 41 | commits = list(repo.iter_commits(paths=pathName+".md")) 42 | prvars("commits") 43 | 44 | h = tem.render( 45 | title = pathName, 46 | siteName = siteName, 47 | pathName = pathName, 48 | nav2 = wiki.locationSitePath(siteName, pathName), 49 | table = getCommitsTable(pathName, commits) 50 | ) 51 | return h 52 | 53 | def getCommitsTable(pathName, commits): 54 | """ 55 | @param commits::[git.Commit] 56 | @return::str containing html 57 | """ 58 | h = """ 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | """ 67 | for co in commits: 68 | path = pathName+".md" 69 | fileData = (co.tree / path).data_stream.read() 70 | prvars("fileData") 71 | 72 | h += form(""" 73 | 74 | 75 | 76 | 77 | 78 | """, 79 | date=niceTime(co.authored_date), 80 | author=co.author.name, 81 | message=htmlEsc(co.message), 82 | size=len(fileData), 83 | hexsha=co.hexsha, 84 | ) 85 | #//for co 86 | h += "
DateAuthorMessageSizeHex SHA
{date}{author}{message}{size}{hexsha}
\n" 87 | return h 88 | 89 | def niceTime(t): 90 | """ Converts a time in seconds since epoch to a nice string """ 91 | nt = time.strftime("%Y-%b-%d %H:%M", 92 | time.gmtime(t)) 93 | return htmlEsc(nt) 94 | 95 | #--------------------------------------------------------------------- 96 | 97 | 98 | #end 99 | -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | # main.py = main module for CatWiki 2 | 3 | import datetime 4 | 5 | from flask import request, redirect 6 | 7 | import allpages 8 | from allpages import * 9 | 10 | import config 11 | 12 | # pages: 13 | import wiki 14 | import sites 15 | import history 16 | 17 | #--------------------------------------------------------------------- 18 | 19 | if __name__ == '__main__': 20 | print "Starting CatWiki..." 21 | app.run(port=config.PORT, debug=True, threaded=True) 22 | 23 | 24 | #end 25 | -------------------------------------------------------------------------------- /app/profiling.py: -------------------------------------------------------------------------------- 1 | # profiling.py = profiling wiki.py 2 | 3 | import cProfile 4 | 5 | import wiki 6 | 7 | #--------------------------------------------------------------------- 8 | 9 | def renderPage(siteName, pathName): 10 | """ render a page """ 11 | h = wiki.wikiPage(siteName, pathName) 12 | print "Rendering %s %s => %d chars" % (siteName, pathName, len(h)) 13 | 14 | # render the index for softpedia: 15 | cProfile.run('renderPage("softpedia", "")') 16 | 17 | # end 18 | -------------------------------------------------------------------------------- /app/server.py: -------------------------------------------------------------------------------- 1 | # server.py = run the web server, using Tornado 2 | 3 | """ 4 | For running CatWiki using Tornado 5 | """ 6 | 7 | 8 | from tornado import wsgi 9 | from tornado import httpserver, ioloop 10 | import main 11 | import config 12 | 13 | if __name__ == "__main__": 14 | container = wsgi.WSGIContainer(main.app) 15 | http_server = httpserver.HTTPServer(container) 16 | http_server.listen(config.PORT) 17 | ioloop.IOLoop.instance().start() 18 | 19 | #end 20 | -------------------------------------------------------------------------------- /app/sites.py: -------------------------------------------------------------------------------- 1 | # sites.py = site-level operations 2 | 3 | import os.path, re 4 | 5 | from flask import request, redirect 6 | 7 | import markdown 8 | 9 | from ulib import butil 10 | from ulib.butil import form 11 | from ulib.debugdec import prvars, pr 12 | 13 | import config 14 | import allpages 15 | from allpages import * 16 | 17 | #--------------------------------------------------------------------- 18 | 19 | @app.route("/") 20 | def front_page(): 21 | return allSites() 22 | 23 | @app.route("/_allSites") 24 | def allSites(): 25 | """ List all the sites for this SiteBox installation 26 | """ 27 | tem = jinjaEnv.get_template("generic_10_2.html") 28 | contents = """\ 29 |

List of all sites

30 | 31 | {siteList} 32 | """.format(siteList=siteListH()) 33 | h = tem.render( 34 | title = "All sites", 35 | nav2 = "" 36 | "", 37 | wikiText = contents, 38 | ) 39 | return h 40 | 41 | def siteListH(): 42 | """ return HTML containing a list of sites 43 | @return::str 44 | """ 45 | h = "

\n" 46 | for stub in config.SITE_STUBS: 47 | _, dirs = butil.getFilesDirs(stub) 48 | for siteName in dirs: 49 | h += (("" 50 | " {siteName} -- " 51 | "under {stub}
\n") 52 | .format(siteName=siteName, 53 | stub=stub)) 54 | #//for siteName 55 | #//for stub 56 | h += "

\n" 57 | return h 58 | 59 | 60 | @app.route("//info") 61 | def siteInfo(siteName): 62 | """ Display information about a site. 63 | """ 64 | import wiki 65 | tem = jinjaEnv.get_template("generic_10_2.html") 66 | 67 | dirPan = wiki.getDirPan(siteName, "") 68 | realPan = os.path.realpath(dirPan) 69 | realPanInfo = "" 70 | if realPan != dirPan: 71 | realPanInfo = form("

Canonical path is {realPan}" 72 | " .

\n", 73 | realPan = realPan) 74 | #prvars("dirPan realPan realPanInfo") 75 | fns, dirs = butil.getFilesDirs(dirPan) 76 | if butil.fileExists(butil.join(dirPan, "home.md")): 77 | homePageInfo = form("View " 78 | " home page.", 79 | siteName = siteName) 80 | else: 81 | homePageInfo = form("""There is no home page. 82 | 83 | 84 | Create one.""", 85 | siteName = siteName) 86 | 87 | contents = """\ 88 |

Information about site {siteName}

89 | 90 |

{siteName} is stored in directory {siteRoot} .

91 | {realPanInfo} 92 | 93 |

There are {numPages} pages in the root folder, and {numSubDirs} sub-folders. 94 | 95 | View root folder. 96 |

97 | 98 |

{homePageInfo}

99 | """.format( 100 | siteName = siteName, 101 | siteRoot = dirPan, 102 | realPanInfo = realPanInfo, 103 | numPages = len(fns), 104 | numSubDirs = len(dirs), 105 | homePageInfo = homePageInfo, 106 | ) 107 | h = tem.render( 108 | 109 | siteName = siteName, 110 | title = "Information about " + siteName, 111 | nav2 = wiki.locationSitePath(siteName, ""), 112 | wikiText = contents, 113 | ) 114 | return h 115 | 116 | #--------------------------------------------------------------------- 117 | 118 | #end 119 | -------------------------------------------------------------------------------- /app/static/app.css: -------------------------------------------------------------------------------- 1 | /* app.css */ 2 | 3 | /*------------------------------------------------------------------*/ 4 | /* local fonts */ 5 | 6 | @font-face { 7 | font-family: 'Ubuntu Mono'; 8 | font-style: normal; 9 | font-weight: 400; 10 | src: url(fonts/UbuntuMono-Regular.ttf) format('truetype'); 11 | } 12 | @font-face { 13 | font-family: 'Ubuntu Mono'; 14 | font-style: normal; 15 | font-weight: 700; 16 | src: url(fonts/UbuntuMono-Bold.ttf) format('truetype'); 17 | } 18 | @font-face { 19 | font-family: 'Ubuntu Mono'; 20 | font-style: italic; 21 | font-weight: 400; 22 | src: url(fonts/UbuntuMono-Italic.ttf) format('truetype'); 23 | } 24 | @font-face { 25 | font-family: 'Ubuntu Mono'; 26 | font-style: italic; 27 | font-weight: 700; 28 | src: url(fonts/UbuntuMono-BoldItalic.ttf) format('truetype'); 29 | } 30 | 31 | 32 | @font-face { 33 | font-family: 'Roboto'; 34 | font-style: normal; 35 | font-weight: 400; 36 | src: url(fonts/Roboto-Regular.ttf) format('truetype'); 37 | } 38 | @font-face { 39 | font-family: 'Roboto'; 40 | font-style: normal; 41 | font-weight: 700; 42 | src: url(fonts/Roboto-Bold.ttf) format('truetype'); 43 | } 44 | @font-face { 45 | font-family: 'Roboto'; 46 | font-style: italic; 47 | font-weight: 400; 48 | src: url(fonts/Roboto-Italic.ttf) format('truetype'); 49 | } 50 | @font-face { 51 | font-family: 'Roboto'; 52 | font-style: italic; 53 | font-weight: 700; 54 | src: url(fonts/Roboto-BoldItalic.ttf) format('truetype'); 55 | } 56 | 57 | /*------------------------------------------------------------------*/ 58 | 59 | tt, pre, code { 60 | font-family: 'Ubuntu Mono', monospace; 61 | font-size: 105%; 62 | } 63 | 64 | body,p,h1,h2,h3,h4,h5,h6,a { 65 | //font-family: 'Open Sans', sans-serif; 66 | font-family: 'Roboto', sans-serif; 67 | } 68 | 69 | textarea { 70 | font-family: 'Ubuntu Mono', monospace; 71 | border: 2px solid #ccc; 72 | } 73 | 74 | body, p { 75 | font-size: 16px; 76 | } 77 | 78 | /*------------------------------------------------------------------*/ 79 | /* modifying bootstrap */ 80 | 81 | hr { 82 | height: 2px; 83 | background: #ccc; 84 | } 85 | 86 | h1,h2,h3,h4,h5,h6 { color: #036; font-weight: bold;} 87 | 88 | h1 { 89 | border-bottom: 2px solid #666; 90 | } 91 | h2 { 92 | border-bottom: 1px dotted; 93 | } 94 | h3 { 95 | font-style: italic; 96 | } 97 | h4 { 98 | font-style: normal; 99 | } 100 | 101 | h1 { font-size: 180%; } 102 | h2 { font-size: 160%; } 103 | h3 { font-size: 140%; } 104 | h4 { font-size: 125%; } 105 | h5 { font-size: 110%; } 106 | h6 { font-size: 100%; } 107 | 108 | 109 | /*------------------------------------------------------------------*/ 110 | /* navbar */ 111 | 112 | .navbar { 113 | border-radius: 0px !important; 114 | margin-bottom: 0px; 115 | } 116 | 117 | /*------------------------------------*/ 118 | /* fix help on navbar top right */ 119 | 120 | .dropdown a { 121 | //padding-left: 10px !important; 122 | //padding-right: 10px !important; 123 | } 124 | 125 | .help-popup a { 126 | padding-right: 1px !important; 127 | } 128 | .help-main a { 129 | padding-left: 4px !important; 130 | } 131 | 132 | /*------------------------------------------------------------------*/ 133 | /* tables */ 134 | 135 | table.report_table { 136 | border: 1px #aaa solid; 137 | border-collapse: collapse; 138 | margin: 7px 0 7px 0; 139 | background: #fff; 140 | } 141 | 142 | table.report_table th { 143 | border: 1px solid #aaa; 144 | padding: 4px 6px 2px 6px; 145 | } 146 | 147 | 148 | table.report_table tbody tr:nth-child(odd) { 149 | //background: #eeeeee; 150 | } 151 | 152 | table.report_table td { 153 | border: 1px solid #aaa; 154 | padding: 3px 6px 1px 6px; 155 | vertical-align: top; 156 | } 157 | 158 | /* tablesorter */ 159 | 160 | table.tablesorter th.header { 161 | cursor: pointer; 162 | //background: #fff; 163 | } 164 | 165 | table.tablesorter th.header:after { 166 | font-family: FontAwesome; 167 | font-style: normal; 168 | font-weight: normal; 169 | line-height: 1; 170 | content: "\f0dc"; 171 | padding-left: 5px; 172 | } 173 | 174 | th.headerSortDown, th.headerSortUp, 175 | th.headerSortDown:hover, th.headerSortUp:hover 176 | { 177 | color: #000 !important; 178 | background: #bcf !important; 179 | } 180 | 181 | table.tablesorter th.headerSortDown:after { 182 | /* up arrow */ 183 | content: "\f0de"; 184 | } 185 | 186 | table.tablesorter th.headerSortUp:after { 187 | /* down arrow */ 188 | content: "\f0dd"; 189 | } 190 | 191 | 192 | /*------------------------------------------------------------------*/ 193 | /* forms */ 194 | 195 | 196 | .form-table tr { 197 | //background: #0f0; 198 | //padding: 40px !important; 199 | //border: 2px solid #900; 200 | } 201 | 202 | .form-table th { 203 | vertical-align: top; 204 | padding: 6px 10px 6px 0px; 205 | } 206 | 207 | .form-table td { 208 | padding: 3px 10px 3px 0px; 209 | } 210 | 211 | .form-table input { 212 | padding: 1px 5px; 213 | } 214 | 215 | .form-table .btn { 216 | padding: 4px 8px; 217 | } 218 | 219 | /* make sure there is enough space before buttons */ 220 | 221 | td > .btn { 222 | margin-top: 12px; 223 | } 224 | 225 | .form-error { 226 | /* an error message in a form */ 227 | color: #800; 228 | background: #fee; 229 | padding: 3px 6px 3px 6px; 230 | border: 1px solid #c66; 231 | } 232 | 233 | input.gin, textarea.gin { 234 | /* green input box -- similar to alphabet */ 235 | padding: 1px 2px 1px 2px; 236 | border: 2px solid #060; 237 | border-radius: 4px; 238 | } 239 | 240 | 241 | .form-table label { 242 | float: right; 243 | } 244 | 245 | /*------------------------------------------------------------------*/ 246 | /* wiki pages */ 247 | 248 | .wiki-navigation { 249 | padding: 5px 10px 0px 30px; 250 | //padding-bottom: 3px; 251 | border-bottom: 2px solid #ccc; 252 | } 253 | 254 | .wiki-commands { 255 | //background: #fff4f4; 256 | } 257 | .command-header { 258 | margin-top: 5px; 259 | font-size: 20px; 260 | font-weight: bold; 261 | } 262 | 263 | .loc-sitename, .loc-sitename a{ 264 | /* the site name on the location bar */ 265 | color: #060; 266 | font-weight: bold; 267 | //background: #eeffee; 268 | //padding: 1px 4px; 269 | //border: 1px solid #9c9; 270 | //border-radius: 4px; 271 | } 272 | 273 | .loc-sitename a:hover { 274 | text-decoration: none; 275 | color: #faf; 276 | background: #606; 277 | } 278 | 279 | .loc-wiki-path { /* the wiki path on the location bar */ 280 | color: #036; 281 | font-weight: bold; 282 | //background: #eeeeff; 283 | //padding: 1px 4px; 284 | //border: 1px solid #9ac; 285 | //border-radius: 4px; 286 | } 287 | .loc-sitename a, .loc-wiki-path a { 288 | padding:1px 2px; 289 | } 290 | .loc-wiki-path a:hover { 291 | text-decoration: none; 292 | //color: #606; 293 | //background: #f8f; 294 | color: #faf; 295 | background: #606; 296 | } 297 | 298 | /*------------------------------------------------------------------*/ 299 | /* index pages */ 300 | 301 | .home-icon { 302 | } 303 | 304 | img.index_image { 305 | padding: 0px; 306 | border: 1px solid #666; 307 | margin-bottom: 10px; 308 | max-width: 120px; 309 | max-height: 120px; 310 | } 311 | 312 | /*------------------------------------------------------------------*/ 313 | /* inside a wiki page (.wiki-contents ) */ 314 | 315 | /* table of contents */ 316 | .toc { 317 | min-width: 200px; 318 | display: table; 319 | border: 1px solid #666; 320 | background: #f4f4f4; 321 | margin: 3px 20px 6px 0px; 322 | padding: 4px 12px 0px 8px; 323 | } 324 | .toc:before { 325 | content: "Table of contents"; 326 | font-weight: bold; 327 | } 328 | .toc li:before { content: ""; } 329 | 330 | 331 | .wiki-contents, .wiki-index { 332 | //background: #f4f4ff; 333 | //margin-left: 20px; 334 | //margin: 0 20px 20px 20px; 335 | padding-bottom: 20px; 336 | } 337 | 338 | .wiki-contents a, .wiki-index a { color: #009; } 339 | .wiki-contents a:hover, .wiki-index a:hover { 340 | background: #e4e4ff; 341 | //text-decoration: none; 342 | text-shadow: 1px 1px 1px #88a; 343 | } 344 | 345 | .wiki-contents h1, .wiki-index h1 { 346 | text-shadow: 2px 2px 2px #bbb; 347 | padding-top: 0px; 348 | margin-top: 5px; 349 | } 350 | 351 | body, .wiki-contents { 352 | counter-reset: h2; 353 | } 354 | 355 | .wiki-contents h2 {counter-reset: h3} 356 | .wiki-contents h3 {counter-reset: h4} 357 | .wiki-contents h4 {counter-reset: h5} 358 | 359 | .wiki-contents h2:before { 360 | counter-increment: h2; 361 | content: counter(h2) ". " 362 | } 363 | .wiki-contents h3:before { 364 | counter-increment: h3; 365 | content: counter(h2) "." counter(h3) ". "; 366 | } 367 | .wiki-contents h4:before { 368 | counter-increment: h4; 369 | content: counter(h2) "." counter(h3) "." counter(h4) ". "; 370 | } 371 | .wiki-contents h5:before { 372 | counter-increment: h5; 373 | content: counter(h2) "." counter(h3) "." 374 | counter(h4) "." counter(h5) ". "; 375 | } 376 | 377 | h2.nocount:before, h3.nocount:before, h4.nocount:before, 378 | h5.nocount:before, h6.nocount:before 379 | { 380 | content: ""; 381 | counter-increment: none; 382 | } 383 | 384 | 385 | pre { 386 | line-height: 16px; 387 | padding: 2px 6px 3px 5px; 388 | margin-left: 25px; 389 | margin-right: 25px; 390 | //border: 2px solid #aaa; 391 | //border: 2px solid #b4d7ff; 392 | border: 2px solid #ebc; 393 | white-space: pre-wrap; 394 | word-break: keep-all; 395 | background: #fff; 396 | color: #000; 397 | } 398 | 399 | code { 400 | font-weight: bold; 401 | color: #602; 402 | background: #fff; 403 | padding: 0 1px 0 1px; 404 | //border: 1px solid #b4d7ff; 405 | border-radius: 4px; 406 | } 407 | 408 | .wiki-contents table code { 409 | padding: 0px; 410 | border: 0px; 411 | } 412 | 413 | .codehilite pre { 414 | /* having a non-white background doesn't look so good with codehilite */ 415 | background: #fff; 416 | color: #000; 417 | } 418 | 419 | blockquote { 420 | padding: 2px 8px; 421 | //border-top: 1px solid #aaa; 422 | //border-bottom: 1px solid #aaa; 423 | //border-right: 1px solid #aaa; 424 | border-left: 3px solid #aaa; 425 | margin: 6px 20px; 426 | } 427 | 428 | textarea#source { 429 | width: 98%; 430 | } 431 | 432 | 433 | .wiki-contents table { 434 | border: 1px solid #aaa; 435 | border-collapse: collapse; 436 | margin: 9px 0 9px 0; 437 | background: #fff; 438 | } 439 | .wiki-contents th { 440 | border: 1px solid #aaa; 441 | padding: 3px 6px 3px 6px; 442 | background: #d8e8ff; 443 | } 444 | .wiki-contents td { 445 | border: 1px solid #aaa; 446 | padding: 2px 6px 2px 6px; 447 | vertical-align: top; 448 | } 449 | 450 | 451 | /* external links display font awesome fa-external-link glyph */ 452 | a[href*="//"]:after { 453 | font-size: 80%; 454 | font-family: 'FontAwesome'; 455 | content: " \f08e"; 456 | } 457 | 458 | /* 459 | a[href*="//"]:before { 460 | font-family: 'FontAwesome'; 461 | padding-right: 0.2em; 462 | content: "\f14c"; 463 | } 464 | */ 465 | 466 | .wiki-contents img { 467 | padding: 2px; 468 | //border: 1px dashed #666; 469 | box-shadow: 0px 0px 9px 2px #888; 470 | max-width: 90%; 471 | } 472 | 473 | /*------------------------------------------------------------------*/ 474 | /* This is a bodge so that inside a table of contents, it makes the 475 | first item, which will be the

title of the page, invisible */ 476 | 477 | div.toc > ul { 478 | padding-left: 20px; 479 | } 480 | 481 | div.toc > ul > li > a { 482 | display: none; 483 | } 484 | 485 | div.toc > ul > li { 486 | list-style-type: none; 487 | } 488 | 489 | div.toc > ul > li > ul { 490 | margin-left: 0px; 491 | padding-left: 0px; 492 | } 493 | 494 | /*------------------------------------------------------------------*/ 495 | /* editing and toolbar */ 496 | 497 | #edit_toolbar { 498 | font-size: 18px; 499 | //color: #060; 500 | //background: #efe; 501 | margin: 9px 0px 3px 0px; 502 | } 503 | 504 | #edit_toolbar .fa { 505 | cursor: pointer; 506 | padding: 3px 4px; 507 | } 508 | 509 | #edit_toolbar span :hover { 510 | color: #faf; 511 | background: #606; 512 | } 513 | 514 | tt.mono_tool { 515 | font-size: 115%; 516 | padding: 2px 2px 0px; 517 | } 518 | 519 | /*------------------------------------------------------------------*/ 520 | /* debugging */ 521 | 522 | .debug { 523 | background: #feb; 524 | color: #630; 525 | padding: 2px 4px; 526 | border: 1px solid #a96; 527 | border-radius: 5px; 528 | } 529 | 530 | .debug { 531 | /* make debugging stuff not appear on page */ 532 | //visibility: collapse; 533 | //display: none; 534 | } 535 | 536 | /*------------------------------------------------------------------*/ 537 | /* printing */ 538 | 539 | 540 | @media print { 541 | p,a,i,b,body { 542 | font-size: 10pt; 543 | line-height: 14pt; 544 | } 545 | tt, code, pre { 546 | font-size: 10pt; 547 | } 548 | 549 | .wiki-navigation a:after, .loc-wiki-path a:after, 550 | a:after, a:link:after { 551 | content:"";/* don't print URL */ 552 | } 553 | 554 | .wiki-commands { 555 | visibility: collapse; 556 | display: none; 557 | } 558 | 559 | 560 | } 561 | 562 | /*------------------------------------------------------------------*/ 563 | /* cat icon */ 564 | 565 | @font-face { 566 | font-family: 'icomoon'; 567 | src:url('fonts/icomoon.eot?qnlk2l'); 568 | src:url('fonts/icomoon.eot?#iefixqnlk2l') format('embedded-opentype'), 569 | url('fonts/icomoon.woff?qnlk2l') format('woff'), 570 | url('fonts/icomoon.ttf?qnlk2l') format('truetype'), 571 | url('fonts/icomoon.svg?qnlk2l#icomoon') format('svg'); 572 | font-weight: normal; 573 | font-style: normal; 574 | } 575 | 576 | [class^="icon-"], [class*=" icon-"] { 577 | font-family: 'icomoon'; 578 | speak: none; 579 | font-style: normal; 580 | font-weight: normal; 581 | font-variant: normal; 582 | text-transform: none; 583 | line-height: 1; 584 | 585 | /* Better Font Rendering =========== */ 586 | //-webkit-font-smoothing: antialiased; 587 | //-moz-osx-font-smoothing: grayscale; 588 | } 589 | 590 | .icon-icon_e600:before { 591 | content: "\e600"; 592 | } 593 | .icon-icon_e601:before { 594 | content: "\e601"; 595 | } 596 | 597 | /*------------------------------------------------------------------*/ 598 | 599 | /*end*/ 600 | -------------------------------------------------------------------------------- /app/static/app.js: -------------------------------------------------------------------------------- 1 | /* app.js */ 2 | 3 | //-------------------------------------------------------------------- 4 | /* help pages */ 5 | 6 | function openHelp(helpPage){ 7 | /* open a help page in a new window */ 8 | var url = "/help/" + helpPage; 9 | open(url, "help", 10 | "width=500, height=600,resizable=1,scrollbars=1," 11 | + "menubar=0,personalbar=0,toolbar=1"); 12 | 13 | } 14 | 15 | //-------------------------------------------------------------------- 16 | /* editting on wikiedit page */ 17 | 18 | function addAround(b, a) { 19 | /* add text before and after the selected text */ 20 | ($("#source") 21 | .selection('insert', {text: b, mode: 'before'}) 22 | .selection('insert', {text: a, mode: 'after'})); 23 | } 24 | 25 | function addTable() { 26 | var t = ("\nHead 1 | Head 2 | Head 3\n" 27 | + "------ | ------ | ------\n" 28 | + "cell 1 | cell 2 | cell 3\n" 29 | + "cell 4 | cell 5 | cell 6\n"); 30 | addAround(t, ""); 31 | } 32 | 33 | function blockquote() { 34 | var sel = $('#source').selection(); // selected text 35 | var lines = sel.split("\n"); 36 | //console.log("lines=" + toString(lines)); 37 | var bqLines = lines.map(line => { 38 | return "> " + line; 39 | }); 40 | //console.log("bqLines=" + toString(bqLines)); 41 | var bqSel = bqLines.join("\n"); 42 | $('#source').selection('replace', {text: bqSel}); 43 | } 44 | 45 | function bulletList() { 46 | var sel = $('#source').selection(); 47 | var lines = sel.split("\n"); 48 | var rLines = lines.map(line => { 49 | return "* " + line; 50 | }); 51 | var r = rLines.join("\n"); 52 | $('#source').selection('replace', {text: r}); 53 | } 54 | 55 | function numberedList() { 56 | var sel = $('#source').selection(); 57 | var lines = sel.split("\n"); 58 | var n = 1; 59 | var rLines = lines.map(line => { 60 | return (n++) + ". " + line; 61 | }); 62 | var r = rLines.join("\n"); 63 | $('#source').selection('replace', {text: r}); 64 | } 65 | 66 | function monospace() { 67 | /* Make text monospace. If it is all on one line, suround with `...`, 68 | else suround with ```...``` */ 69 | var sel = $('#source').selection(); 70 | var lines = sel.split("\n"); 71 | if (lines.length <= 1){ 72 | addAround("`", "`"); 73 | } else { 74 | addAround("\n```\n", "\n```\n"); 75 | } 76 | } 77 | 78 | //-------------------------------------------------------------------- 79 | 80 | //end 81 | -------------------------------------------------------------------------------- /app/static/catwiki.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/catwiki.ico -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/HELP-US-OUT.txt: -------------------------------------------------------------------------------- 1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project, 2 | Fort Awesome (https://fortawesome.com). It makes it easy to put the perfect icons on your website. Choose from our awesome, 3 | comprehensive icon sets or copy and paste your own. 4 | 5 | Please. Check it out. 6 | 7 | -Dave Gandy 8 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/font-awesome-4.7.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/font-awesome-4.7.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/font-awesome-4.7.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { float: left; } 11 | .@{fa-css-prefix}-pull-right { float: right; } 12 | 13 | .@{fa-css-prefix} { 14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .@{fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | @import "screen-reader.less"; 19 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | .sr-only() { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | .sr-only-focusable() { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/less/screen-reader.less: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { .sr-only(); } 5 | .sr-only-focusable { .sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { float: left; } 11 | .#{$fa-css-prefix}-pull-right { float: right; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .#{$fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; 16 | -webkit-transform: rotate($degrees); 17 | -ms-transform: rotate($degrees); 18 | transform: rotate($degrees); 19 | } 20 | 21 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; 23 | -webkit-transform: scale($horiz, $vert); 24 | -ms-transform: scale($horiz, $vert); 25 | transform: scale($horiz, $vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | @mixin sr-only { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | @mixin sr-only-focusable { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/scss/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { @include sr-only(); } 5 | .sr-only-focusable { @include sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /app/static/font-awesome-4.7.0/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | @import "screen-reader"; 19 | -------------------------------------------------------------------------------- /app/static/fonts/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /app/static/fonts/UFL.txt: -------------------------------------------------------------------------------- 1 | ------------------------------- 2 | UBUNTU FONT LICENCE Version 1.0 3 | ------------------------------- 4 | 5 | PREAMBLE 6 | This licence allows the licensed fonts to be used, studied, modified and 7 | redistributed freely. The fonts, including any derivative works, can be 8 | bundled, embedded, and redistributed provided the terms of this licence 9 | are met. The fonts and derivatives, however, cannot be released under 10 | any other licence. The requirement for fonts to remain under this 11 | licence does not require any document created using the fonts or their 12 | derivatives to be published under this licence, as long as the primary 13 | purpose of the document is not to be a vehicle for the distribution of 14 | the fonts. 15 | 16 | DEFINITIONS 17 | "Font Software" refers to the set of files released by the Copyright 18 | Holder(s) under this licence and clearly marked as such. This may 19 | include source files, build scripts and documentation. 20 | 21 | "Original Version" refers to the collection of Font Software components 22 | as received under this licence. 23 | 24 | "Modified Version" refers to any derivative made by adding to, deleting, 25 | or substituting -- in part or in whole -- any of the components of the 26 | Original Version, by changing formats or by porting the Font Software to 27 | a new environment. 28 | 29 | "Copyright Holder(s)" refers to all individuals and companies who have a 30 | copyright ownership of the Font Software. 31 | 32 | "Substantially Changed" refers to Modified Versions which can be easily 33 | identified as dissimilar to the Font Software by users of the Font 34 | Software comparing the Original Version with the Modified Version. 35 | 36 | To "Propagate" a work means to do anything with it that, without 37 | permission, would make you directly or secondarily liable for 38 | infringement under applicable copyright law, except executing it on a 39 | computer or modifying a private copy. Propagation includes copying, 40 | distribution (with or without modification and with or without charging 41 | a redistribution fee), making available to the public, and in some 42 | countries other activities as well. 43 | 44 | PERMISSION & CONDITIONS 45 | This licence does not grant any rights under trademark law and all such 46 | rights are reserved. 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a 49 | copy of the Font Software, to propagate the Font Software, subject to 50 | the below conditions: 51 | 52 | 1) Each copy of the Font Software must contain the above copyright 53 | notice and this licence. These can be included either as stand-alone 54 | text files, human-readable headers or in the appropriate machine- 55 | readable metadata fields within text or binary files as long as those 56 | fields can be easily viewed by the user. 57 | 58 | 2) The font name complies with the following: 59 | (a) The Original Version must retain its name, unmodified. 60 | (b) Modified Versions which are Substantially Changed must be renamed to 61 | avoid use of the name of the Original Version or similar names entirely. 62 | (c) Modified Versions which are not Substantially Changed must be 63 | renamed to both (i) retain the name of the Original Version and (ii) add 64 | additional naming elements to distinguish the Modified Version from the 65 | Original Version. The name of such Modified Versions must be the name of 66 | the Original Version, with "derivative X" where X represents the name of 67 | the new work, appended to that name. 68 | 69 | 3) The name(s) of the Copyright Holder(s) and any contributor to the 70 | Font Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except (i) as required by this licence, (ii) to 72 | acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with 73 | their explicit written permission. 74 | 75 | 4) The Font Software, modified or unmodified, in part or in whole, must 76 | be distributed entirely under this licence, and must not be distributed 77 | under any other licence. The requirement for fonts to remain under this 78 | licence does not affect any document created using the Font Software, 79 | except any version of the Font Software extracted from a document 80 | created using the Font Software may only be distributed under this 81 | licence. 82 | 83 | TERMINATION 84 | This licence becomes null and void if any of the above conditions are 85 | not met. 86 | 87 | DISCLAIMER 88 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 89 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 90 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF 91 | COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 92 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 93 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 94 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 95 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER 96 | DEALINGS IN THE FONT SOFTWARE. 97 | -------------------------------------------------------------------------------- /app/static/fonts/UbuntuMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/fonts/UbuntuMono-Bold.ttf -------------------------------------------------------------------------------- /app/static/fonts/UbuntuMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/fonts/UbuntuMono-BoldItalic.ttf -------------------------------------------------------------------------------- /app/static/fonts/UbuntuMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/fonts/UbuntuMono-Italic.ttf -------------------------------------------------------------------------------- /app/static/fonts/UbuntuMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/fonts/UbuntuMono-Regular.ttf -------------------------------------------------------------------------------- /app/static/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/fonts/icomoon.eot -------------------------------------------------------------------------------- /app/static/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/static/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/fonts/icomoon.ttf -------------------------------------------------------------------------------- /app/static/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/static/fonts/icomoon.woff -------------------------------------------------------------------------------- /app/static/jquery.selection.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery.selection - jQuery Plugin 3 | * 4 | * Copyright (c) 2010-2014 IWASAKI Koji (@madapaja). 5 | * http://blog.madapaja.net/ 6 | * Under The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining 9 | * a copy of this software and associated documentation files (the 10 | * "Software"), to deal in the Software without restriction, including 11 | * without limitation the rights to use, copy, modify, merge, publish, 12 | * distribute, sublicense, and/or sell copies of the Software, and to 13 | * permit persons to whom the Software is furnished to do so, subject to 14 | * the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be 17 | * included in all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | (function($, win, doc) { 28 | /** 29 | * get caret status of the selection of the element 30 | * 31 | * @param {Element} element target DOM element 32 | * @return {Object} return 33 | * @return {String} return.text selected text 34 | * @return {Number} return.start start position of the selection 35 | * @return {Number} return.end end position of the selection 36 | */ 37 | var _getCaretInfo = function(element){ 38 | var res = { 39 | text: '', 40 | start: 0, 41 | end: 0 42 | }; 43 | 44 | if (!element.value) { 45 | /* no value or empty string */ 46 | return res; 47 | } 48 | 49 | try { 50 | if (win.getSelection) { 51 | /* except IE */ 52 | res.start = element.selectionStart; 53 | res.end = element.selectionEnd; 54 | res.text = element.value.slice(res.start, res.end); 55 | } else if (doc.selection) { 56 | /* for IE */ 57 | element.focus(); 58 | 59 | var range = doc.selection.createRange(), 60 | range2 = doc.body.createTextRange(); 61 | 62 | res.text = range.text; 63 | 64 | try { 65 | range2.moveToElementText(element); 66 | range2.setEndPoint('StartToStart', range); 67 | } catch (e) { 68 | range2 = element.createTextRange(); 69 | range2.setEndPoint('StartToStart', range); 70 | } 71 | 72 | res.start = element.value.length - range2.text.length; 73 | res.end = res.start + range.text.length; 74 | } 75 | } catch (e) { 76 | /* give up */ 77 | } 78 | 79 | return res; 80 | }; 81 | 82 | /** 83 | * caret operation for the element 84 | * @type {Object} 85 | */ 86 | var _CaretOperation = { 87 | /** 88 | * get caret position 89 | * 90 | * @param {Element} element target element 91 | * @return {Object} return 92 | * @return {Number} return.start start position for the selection 93 | * @return {Number} return.end end position for the selection 94 | */ 95 | getPos: function(element) { 96 | var tmp = _getCaretInfo(element); 97 | return {start: tmp.start, end: tmp.end}; 98 | }, 99 | 100 | /** 101 | * set caret position 102 | * 103 | * @param {Element} element target element 104 | * @param {Object} toRange caret position 105 | * @param {Number} toRange.start start position for the selection 106 | * @param {Number} toRange.end end position for the selection 107 | * @param {String} caret caret mode: any of the following: "keep" | "start" | "end" 108 | */ 109 | setPos: function(element, toRange, caret) { 110 | caret = this._caretMode(caret); 111 | 112 | if (caret === 'start') { 113 | toRange.end = toRange.start; 114 | } else if (caret === 'end') { 115 | toRange.start = toRange.end; 116 | } 117 | 118 | element.focus(); 119 | try { 120 | if (element.createTextRange) { 121 | var range = element.createTextRange(); 122 | 123 | if (win.navigator.userAgent.toLowerCase().indexOf("msie") >= 0) { 124 | toRange.start = element.value.substr(0, toRange.start).replace(/\r/g, '').length; 125 | toRange.end = element.value.substr(0, toRange.end).replace(/\r/g, '').length; 126 | } 127 | 128 | range.collapse(true); 129 | range.moveStart('character', toRange.start); 130 | range.moveEnd('character', toRange.end - toRange.start); 131 | 132 | range.select(); 133 | } else if (element.setSelectionRange) { 134 | element.setSelectionRange(toRange.start, toRange.end); 135 | } 136 | } catch (e) { 137 | /* give up */ 138 | } 139 | }, 140 | 141 | /** 142 | * get selected text 143 | * 144 | * @param {Element} element target element 145 | * @return {String} return selected text 146 | */ 147 | getText: function(element) { 148 | return _getCaretInfo(element).text; 149 | }, 150 | 151 | /** 152 | * get caret mode 153 | * 154 | * @param {String} caret caret mode 155 | * @return {String} return any of the following: "keep" | "start" | "end" 156 | */ 157 | _caretMode: function(caret) { 158 | caret = caret || "keep"; 159 | if (caret === false) { 160 | caret = 'end'; 161 | } 162 | 163 | switch (caret) { 164 | case 'keep': 165 | case 'start': 166 | case 'end': 167 | break; 168 | 169 | default: 170 | caret = 'keep'; 171 | } 172 | 173 | return caret; 174 | }, 175 | 176 | /** 177 | * replace selected text 178 | * 179 | * @param {Element} element target element 180 | * @param {String} text replacement text 181 | * @param {String} caret caret mode: any of the following: "keep" | "start" | "end" 182 | */ 183 | replace: function(element, text, caret) { 184 | var tmp = _getCaretInfo(element), 185 | orig = element.value, 186 | pos = $(element).scrollTop(), 187 | range = {start: tmp.start, end: tmp.start + text.length}; 188 | 189 | element.value = orig.substr(0, tmp.start) + text + orig.substr(tmp.end); 190 | 191 | $(element).scrollTop(pos); 192 | this.setPos(element, range, caret); 193 | }, 194 | 195 | /** 196 | * insert before the selected text 197 | * 198 | * @param {Element} element target element 199 | * @param {String} text insertion text 200 | * @param {String} caret caret mode: any of the following: "keep" | "start" | "end" 201 | */ 202 | insertBefore: function(element, text, caret) { 203 | var tmp = _getCaretInfo(element), 204 | orig = element.value, 205 | pos = $(element).scrollTop(), 206 | range = {start: tmp.start + text.length, end: tmp.end + text.length}; 207 | 208 | element.value = orig.substr(0, tmp.start) + text + orig.substr(tmp.start); 209 | 210 | $(element).scrollTop(pos); 211 | this.setPos(element, range, caret); 212 | }, 213 | 214 | /** 215 | * insert after the selected text 216 | * 217 | * @param {Element} element target element 218 | * @param {String} text insertion text 219 | * @param {String} caret caret mode: any of the following: "keep" | "start" | "end" 220 | */ 221 | insertAfter: function(element, text, caret) { 222 | var tmp = _getCaretInfo(element), 223 | orig = element.value, 224 | pos = $(element).scrollTop(), 225 | range = {start: tmp.start, end: tmp.end}; 226 | 227 | element.value = orig.substr(0, tmp.end) + text + orig.substr(tmp.end); 228 | 229 | $(element).scrollTop(pos); 230 | this.setPos(element, range, caret); 231 | } 232 | }; 233 | 234 | /* add jQuery.selection */ 235 | $.extend({ 236 | /** 237 | * get selected text on the window 238 | * 239 | * @param {String} mode selection mode: any of the following: "text" | "html" 240 | * @return {String} return 241 | */ 242 | selection: function(mode) { 243 | var getText = ((mode || 'text').toLowerCase() === 'text'); 244 | 245 | try { 246 | if (win.getSelection) { 247 | if (getText) { 248 | // get text 249 | return win.getSelection().toString(); 250 | } else { 251 | // get html 252 | var sel = win.getSelection(), range; 253 | 254 | if (sel.getRangeAt) { 255 | range = sel.getRangeAt(0); 256 | } else { 257 | range = doc.createRange(); 258 | range.setStart(sel.anchorNode, sel.anchorOffset); 259 | range.setEnd(sel.focusNode, sel.focusOffset); 260 | } 261 | 262 | return $('
').append(range.cloneContents()).html(); 263 | } 264 | } else if (doc.selection) { 265 | if (getText) { 266 | // get text 267 | return doc.selection.createRange().text; 268 | } else { 269 | // get html 270 | return doc.selection.createRange().htmlText; 271 | } 272 | } 273 | } catch (e) { 274 | /* give up */ 275 | } 276 | 277 | return ''; 278 | } 279 | }); 280 | 281 | /* add selection */ 282 | $.fn.extend({ 283 | selection: function(mode, opts) { 284 | opts = opts || {}; 285 | 286 | switch (mode) { 287 | /** 288 | * selection('getPos') 289 | * get caret position 290 | * 291 | * @return {Object} return 292 | * @return {Number} return.start start position for the selection 293 | * @return {Number} return.end end position for the selection 294 | */ 295 | case 'getPos': 296 | return _CaretOperation.getPos(this[0]); 297 | 298 | /** 299 | * selection('setPos', opts) 300 | * set caret position 301 | * 302 | * @param {Number} opts.start start position for the selection 303 | * @param {Number} opts.end end position for the selection 304 | */ 305 | case 'setPos': 306 | return this.each(function() { 307 | _CaretOperation.setPos(this, opts); 308 | }); 309 | 310 | /** 311 | * selection('replace', opts) 312 | * replace the selected text 313 | * 314 | * @param {String} opts.text replacement text 315 | * @param {String} opts.caret caret mode: any of the following: "keep" | "start" | "end" 316 | */ 317 | case 'replace': 318 | return this.each(function() { 319 | _CaretOperation.replace(this, opts.text, opts.caret); 320 | }); 321 | 322 | /** 323 | * selection('insert', opts) 324 | * insert before/after the selected text 325 | * 326 | * @param {String} opts.text insertion text 327 | * @param {String} opts.caret caret mode: any of the following: "keep" | "start" | "end" 328 | * @param {String} opts.mode insertion mode: any of the following: "before" | "after" 329 | */ 330 | case 'insert': 331 | return this.each(function() { 332 | if (opts.mode === 'before') { 333 | _CaretOperation.insertBefore(this, opts.text, opts.caret); 334 | } else { 335 | _CaretOperation.insertAfter(this, opts.text, opts.caret); 336 | } 337 | }); 338 | 339 | /** 340 | * selection('get') 341 | * get selected text 342 | * 343 | * @return {String} return 344 | */ 345 | case 'get': 346 | /* falls through */ 347 | default: 348 | return _CaretOperation.getText(this[0]); 349 | } 350 | 351 | return this; 352 | } 353 | }); 354 | })(jQuery, window, window.document); 355 | -------------------------------------------------------------------------------- /app/static/jsfunctions.js: -------------------------------------------------------------------------------- 1 | /* jsfunctions.js */ 2 | 3 | /***** 4 | Some javascript utility functions 5 | *****/ 6 | 7 | //-------------------------------------------------------------------- 8 | 9 | function ObjToSource(o){ 10 | if (!o) return 'null'; 11 | if (typeof(o) == "object") { 12 | if (!ObjToSource.check) ObjToSource.check = new Array(); 13 | for (var i=0, k=ObjToSource.check.length ; i el!==item); 78 | } 79 | 80 | 81 | function including(a, item) { 82 | /* return an array like (a) but containing item (item). 83 | If it alrady contains it, just return (a). 84 | Else return a new array with (item) in it. 85 | */ 86 | if (contains(a, item)) { 87 | return a; 88 | } else { 89 | var clone = a.slice(0); 90 | clone.push(item); 91 | return clone; 92 | } 93 | } 94 | 95 | function makeIdDict(obs){ 96 | /* 97 | obs::[Object] has an 'id' key 98 | returns::{str:Object} where the keys are the ids 99 | from (obs) 100 | */ 101 | var idDict = {}; 102 | obs.forEach(ob => { 103 | var id = get(ob, 'id', ""); 104 | if (id !== ""){ 105 | idDict[id] = ob; 106 | } 107 | }); 108 | return idDict; 109 | } 110 | 111 | function minimum(a){ 112 | /* return the minimum value in an array */ 113 | if (a.length===0) return null; 114 | var min; 115 | var first = true; 116 | a.forEach(e => { 117 | if (first){ 118 | min = e; 119 | } else { 120 | if (e < min) min = e; 121 | } 122 | first = false; 123 | }); 124 | return min; 125 | } 126 | 127 | //-------------------------------------------------------------------- 128 | /* functions on strings */ 129 | 130 | function removeSpaces(s) { 131 | /* return a string like (s) but with all spaces removed */ 132 | return s.replace(/ /g, ""); 133 | } 134 | 135 | //-------------------------------------------------------------------- 136 | /***** 137 | From http://stackoverflow.com/questions/5560248 138 | /programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors 139 | 140 | p = proportion of conversion 141 | from = from colour 142 | to = to colour 143 | 144 | *****/ 145 | 146 | function blendColors(c0, c1, p) { 147 | var f=parseInt(c0.slice(1),16), 148 | t=parseInt(c1.slice(1),16), 149 | R1=f>>16, 150 | G1=f>>8&0x00FF, 151 | B1=f&0x0000FF, 152 | R2=t>>16, 153 | G2=t>>8&0x00FF, 154 | B2=t&0x0000FF; 155 | return "#"+(0x1000000+(Math.round((R2-R1)*p)+R1)*0x10000 156 | +(Math.round((G2-G1)*p)+G1)*0x100+( 157 | Math.round((B2-B1)*p)+B1)).toString(16).slice(1); 158 | } 159 | 160 | /* 161 | percent of 0...1 = lighten 162 | percent of 0...-1 = darken 163 | */ 164 | function shadeColor2(color, percent) { 165 | var f=parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF; 166 | return "#"+(0x1000000+(Math.round((t-R)*p)+R)*0x10000+(Math.round((t-G)*p)+G)*0x100+(Math.round((t-B)*p)+B)).toString(16).slice(1); 167 | } 168 | 169 | function lighten(col, amount) { return shadeColor2(col, amount); } 170 | function darken(col, amount) { return shadeColor2(col, -amount); } 171 | 172 | 173 | //-------------------------------------------------------------------- 174 | 175 | /* end */ 176 | -------------------------------------------------------------------------------- /app/static/pygments.css: -------------------------------------------------------------------------------- 1 | /* pygments.css = CSS for pygments code highlighting */ 2 | 3 | 4 | /*------------------------------------------------------------------*/ 5 | /* comments */ 6 | 7 | .codehilite .c { color: #808080 } /* Comment */ 8 | .codehilite .cm { color: #808080 } /* Comment.Multiline */ 9 | .codehilite .cp { color: #507090 } /* Comment.Preproc */ 10 | .codehilite .c1 { color: #808080 } /* Comment.Single */ 11 | .codehilite .cs { color: #cc0000; font-weight: bold } /* Comment.Special */ 12 | 13 | 14 | /*------------------------------------------------------------------*/ 15 | /* generic */ 16 | 17 | .codehilite .gd { color: #A00000 } /* Generic.Deleted */ 18 | .codehilite .ge { font-style: italic } /* Generic.Emph */ 19 | .codehilite .gr { color: #FF0000 } /* Generic.Error */ 20 | .codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 21 | .codehilite .gi { color: #00A000 } /* Generic.Inserted */ 22 | .codehilite .go { color: #808080 } /* Generic.Output */ 23 | .codehilite .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 24 | .codehilite .gs { font-weight: bold } /* Generic.Strong */ 25 | .codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 26 | .codehilite .gt { color: #0040D0 } /* Generic.Traceback */ 27 | 28 | /*------------------------------------------------------------------*/ 29 | /* keywords */ 30 | 31 | .codehilite .k { color: #008000; font-weight: bold } /* Keyword */ 32 | .codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 33 | .codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 34 | .codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 35 | .codehilite .kp { color: #003080; font-weight: bold } /* Keyword.Pseudo */ 36 | .codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 37 | .codehilite .kt { color: #303090; font-weight: bold } /* Keyword.Type */ 38 | 39 | /*------------------------------------------------------------------*/ 40 | /* literals */ 41 | 42 | .codehilite .m { color: #6000E0; font-weight: bold } /* Literal.Number */ 43 | .codehilite .mf { color: #6000E0; font-weight: bold } /* Literal.Number.Float */ 44 | .codehilite .mh { color: #005080; font-weight: bold } /* Literal.Number.Hex */ 45 | .codehilite .mi { color: #0000D0; font-weight: bold } /* Literal.Number.Integer */ 46 | .codehilite .mo { color: #4000E0; font-weight: bold } /* Literal.Number.Oct */ 47 | .codehilite .il { color: #0000D0; font-weight: bold } /* Literal.Number.Integer.Long */ 48 | 49 | .codehilite .s { background-color: #fcc } /* Literal.String */ 50 | .codehilite .sb { background-color: #fff0f0 } /* Literal.String.Backtick */ 51 | .codehilite .sc { color: #0040D0 } /* Literal.String.Char */ 52 | .codehilite .sd { color: #D04020 } /* Literal.String.Doc */ 53 | .codehilite .s2 { background-color: #fcc } /* Literal.String.Double */ 54 | .codehilite .se { color: #606060; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */ 55 | .codehilite .sh { background-color: #fff0f0 } /* Literal.String.Heredoc */ 56 | .codehilite .si { background-color: #e0e0e0 } /* Literal.String.Interpol */ 57 | .codehilite .sx { color: #D02000; background-color: #fcc } /* Literal.String.Other */ 58 | .codehilite .sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */ 59 | .codehilite .s1 { background-color: #fcc } /* Literal.String.Single */ 60 | .codehilite .ss { color: #A06000 } /* Literal.String.Symbol */ 61 | 62 | /*------------------------------------------------------------------*/ 63 | /* names */ 64 | 65 | .codehilite .na { color: #0000C0 } /* Name.Attribute */ 66 | .codehilite .nb { color: #007020 } /* Name.Builtin */ 67 | .codehilite .nc { color: #B00060; font-weight: bold } /* Name.Class */ 68 | .codehilite .no { color: #003060; font-weight: bold } /* Name.Constant */ 69 | .codehilite .nd { color: #505050; font-weight: bold } /* Name.Decorator */ 70 | .codehilite .ni { color: #800000; font-weight: bold } /* Name.Entity */ 71 | .codehilite .ne { color: #F00000; font-weight: bold } /* Name.Exception */ 72 | .codehilite .nf { color: #0060B0; font-weight: bold } /* Name.Function */ 73 | .codehilite .nl { color: #907000; font-weight: bold } /* Name.Label */ 74 | .codehilite .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 75 | .codehilite .nt { color: #007000 } /* Name.Tag */ 76 | .codehilite .nv { color: #906030 } /* Name.Variable */ 77 | .codehilite .bp { color: #007020 } /* Name.Builtin.Pseudo */ 78 | .codehilite .vc { color: #306090 } /* Name.Variable.Class */ 79 | .codehilite .vg { color: #d07000; font-weight: bold } /* Name.Variable.Global */ 80 | .codehilite .vi { color: #3030B0 } /* Name.Variable.Instance */ 81 | 82 | /*------------------------------------------------------------------*/ 83 | /* everything else */ 84 | 85 | 86 | .codehilite .ow { color: #000000; font-weight: bold } /* Operator.Word */ 87 | .codehilite .w { color: #bbbbbb } /* Text.Whitespace */ 88 | .codehilite .hll { background-color: #ffffcc } 89 | .codehilite .err { color: #F00000; background-color: #F0A0A0 } /* Error */ 90 | .codehilite .o { color: #303030 } /* Operator */ 91 | 92 | /*------------------------------------------------------------------*/ 93 | 94 | 95 | /*end*/ 96 | -------------------------------------------------------------------------------- /app/templates/front_page.html: -------------------------------------------------------------------------------- 1 | {# front_page.html 2 | =============== 3 | 4 | Front page for SiteBox 5 | #} 6 | {% extends "main.html" %} 7 | 8 | {% block page_wrapper %} 9 |
10 |
11 |
12 | 13 |

SiteBox!

14 | 15 | {% if currentUser.is_authenticated() %} 16 | 17 |

Hello, 18 | {{currentUser}}. You can: 19 | 20 |

24 | 25 | {% else %} 26 | {{badLoginWarning}} 27 |

Before you can do anything, you must log in:

28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 43 |
40 | 41 | Log in
44 | {% endif %} 45 | 46 |


There is also an online 47 | 48 | help system.

49 | 50 |
51 |
52 |
53 | {% endblock page_wrapper %} 54 | 55 | {% block atend %} 56 | 61 | {% endblock atend %} 62 | 63 | {# end #} -------------------------------------------------------------------------------- /app/templates/generic_10_2.html: -------------------------------------------------------------------------------- 1 | {# generic_10_2.html 2 | ================= 3 | 4 | Generic page layout with: 5 | 6 | - a navigation bar at the top 7 | - a 2-column area with buttons for commands 8 | - a 10-column main area 9 | #} 10 | {% extends "main.html" %} 11 | 12 | {% block title %} 13 | {{title}} 14 | {% endblock %} 15 | 16 | {% block contents %} 17 |
18 |
19 |
20 | {% block wikiNavigation %} 21 |

{{nav2}}

22 | {% endblock wikiNavigation %} 23 |
24 |
25 |
26 |
27 | {% block wikiCommands %} 28 | {% endblock wikiCommands %} 29 |
30 |
31 | {% block wikiContents %} 32 |
33 | {{wikiText}} 34 |
35 | {% endblock wikiContents %} 36 |
37 |
38 |
39 | {% endblock contents %} 40 | 41 | {#end#} 42 | -------------------------------------------------------------------------------- /app/templates/help.html: -------------------------------------------------------------------------------- 1 | {# help.html 2 | ========= 3 | 4 | Template for help pages 5 | 6 | #} 7 | 8 | 9 | 10 | {{title}} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 |
22 | Index 23 | Help 24 |
25 |
26 | 27 |
28 | {{contents}} 29 |
30 | 31 | 32 | {%block atend%} 33 | 34 | {%endblock atend%} 35 | 36 | -------------------------------------------------------------------------------- /app/templates/history.html: -------------------------------------------------------------------------------- 1 | {# history.html 2 | ============= 3 | 4 | History of a page in a wiki 5 | #} 6 | {% extends "wiki_page.html" %} 7 | 8 | {% block wikiCommands %} 9 |

Commands

10 | 11 |

13 | View page

14 | 15 |

17 | Edit page

18 | 19 |

21 | History

22 | {% endblock wikiCommands %} 23 | 24 | {% block wikiContents %} 25 |

History of {{pathName}}

26 | 27 | {{table}} 28 | {% endblock wikiContents %} 29 | {# end #} 30 | -------------------------------------------------------------------------------- /app/templates/main.html: -------------------------------------------------------------------------------- 1 | {# 2 | main.html = the base template 3 | 4 | Uses Twitter Bootstrap 5 | 6 | #} 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% block title %}CatWiki{% endblock %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% block extrahead %}{% endblock extrahead %} 22 | 23 | 24 | 25 | {%block body_start %} 26 | 27 | 28 | 54 | {%endblock topbar %} 55 | {% endblock body_start %} 56 | 57 | {% block contents %} 58 |
59 | {% block page_wrapper %} 60 | 61 |
62 |
{% block body %} 63 |

This is the body of the page.

64 | {% endblock body %}
65 |
66 | {% endblock page_wrapper %} 67 |
68 | {% endblock contents %} 69 | 70 | 71 | 72 | 73 | 74 | 75 | {%block atend%} 76 | 77 | {%endblock atend%} 78 | 79 | -------------------------------------------------------------------------------- /app/templates/user.html: -------------------------------------------------------------------------------- 1 | {# user.html 2 | ========= 3 | 4 | A user. 5 | #} 6 | {% extends "main.html" %} 7 | 8 | {% block body %} 9 |

User: {{doc.userName}}

10 | 11 |

Details of user 12 | {{doc.userName}}. 13 | id={{id}} 14 |

15 | 16 |
17 | 18 | 19 | 20 | 21 | {{buildFormLines}} 22 | 23 | 24 | 34 | 35 |
25 | 26 | 27 | Save user 28 |   29 | 31 | 32 | Delete user 33 |
36 |
37 | {% endblock body %} 38 | 39 | {% block atend %} 40 | 61 | {% endblock atend %} 62 | 63 | {# end #} -------------------------------------------------------------------------------- /app/templates/users.html: -------------------------------------------------------------------------------- 1 | {# users.html 2 | ========== 3 | 4 | A list of users 5 | #} 6 | {% extends "main.html" %} 7 | 8 | {% block body %} 9 |

Users ({{count}})

10 | 11 | {{table}} 12 | 13 |
14 |

15 | 16 | Create new user

17 | {% endblock body %} 18 | 19 | {# end #} -------------------------------------------------------------------------------- /app/templates/wiki_index.html: -------------------------------------------------------------------------------- 1 | {# wiki_index.html 2 | =============== 3 | 4 | An index of the files in a fiolder in a wiki 5 | #} 6 | {% extends "generic_10_2.html" %} 7 | 8 | {% block wikiCommands %} 9 |

Commands

10 | {% endblock wikiCommands %} 11 | 12 | {% block wikiContents %} 13 |
14 | {{wikiText}} 15 |
16 |

17 | {% endblock wikiContents %} 18 | 19 | {# end #} 20 | -------------------------------------------------------------------------------- /app/templates/wiki_page.html: -------------------------------------------------------------------------------- 1 | {# wiki_page.html 2 | ============== 3 | 4 | A page in a wiki 5 | #} 6 | {% extends "generic_10_2.html" %} 7 | 8 | {% block wikiCommands %} 9 |

Commands

10 |

11 | 13 | Edit page

14 | 15 |

17 | History

18 | {% endblock wikiCommands %} 19 | 20 | {% block wikiContents %} 21 |
22 | {{wikiText}} 23 |
24 |

25 | {% endblock wikiContents %} 26 | 27 | {# end #} 28 | -------------------------------------------------------------------------------- /app/templates/wikiedit.html: -------------------------------------------------------------------------------- 1 | {# wikiedit.html 2 | ============= 3 | 4 | Editting a page in a wiki 5 | #} 6 | {% extends "wiki_page.html" %} 7 | 8 | 9 | {% block wikiCommands %} 10 |

Commands

11 | 12 |

13 | Save changes

14 | 15 |

16 | 17 | Cancel changes

18 | 19 |

20 | 21 | Rename article

22 | 23 |
24 |

25 | Delete article

26 | {% endblock wikiCommands %} 27 | 28 | 29 | {% block wikiContents %} 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | mono 55 | 56 |  mark  57 |
58 | 59 |
61 | 62 | 63 |
64 | {% endblock wikiContents %} 65 | 66 | 67 | {% block atend %} 68 | 81 | {% endblock atend %} 82 | {# end #} 83 | -------------------------------------------------------------------------------- /app/ulib/HISTORY: -------------------------------------------------------------------------------- 1 | HISTORY for ulib 2 | ================ 3 | 4 | 4-Mar-2014: 5 | changed so debugging is switched on by default. When 6 | running in production mode, do this: 7 | 8 | import debugdec 9 | debugdec.debugging = False 10 | 11 | Chinging debugdec so messages are written to stderr instead of stdout. 12 | 13 | 14 | 15 | /end/ -------------------------------------------------------------------------------- /app/ulib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/app/ulib/__init__.py -------------------------------------------------------------------------------- /app/ulib/debugdec.py: -------------------------------------------------------------------------------- 1 | # debugdec.py = decorators useful for debugging 2 | 3 | """ 4 | (c) 2013,2014 Philip Hunt, cabalamat@gmail.com 5 | You may use this software under the terms of the MIT license. See file 6 | for details. 7 | """ 8 | 9 | import inspect 10 | import datetime 11 | import functools 12 | import sys 13 | 14 | # if you want to disable this module, set this to False eleswhere. 15 | debugging = True 16 | 17 | #--------------------------------------------------------------------- 18 | 19 | _PRINTARGS_DEPTH = 0 20 | _PRINTARGS_INDENT = "| " 21 | 22 | def printargs(fn): 23 | if not debugging: 24 | return fn 25 | @functools.wraps(fn) 26 | def wrapper(*args, **kwargs): 27 | global _PRINTARGS_DEPTH 28 | argStr = ", ".join([repr(a) for a in args]) 29 | kwargStr = ", ".join(["%s=%r"%(k,v) for v,k in enumerate(kwargs)]) 30 | comma = "" 31 | if argStr and kwargStr: comma = ", " 32 | akStr = argStr + comma + kwargStr 33 | print '%s%s(%s)' % (_PRINTARGS_INDENT * _PRINTARGS_DEPTH, 34 | fn.__name__, akStr) 35 | _PRINTARGS_DEPTH += 1 36 | retVal = fn(*args, **kwargs) 37 | _PRINTARGS_DEPTH -= 1 38 | if retVal != None: 39 | print "%s%s(%s) => %r" % (_PRINTARGS_INDENT * _PRINTARGS_DEPTH, 40 | fn.__name__, akStr, 41 | retVal) 42 | return retVal 43 | return wrapper 44 | 45 | #--------------------------------------------------------------------- 46 | """ 47 | This decorator prints to stdout how long a function took to run. 48 | """ 49 | 50 | def timing(fn): 51 | if not debugging: 52 | return fn 53 | def wrapper(*args, **kwargs): 54 | before = datetime.datetime.now() 55 | retVal = fn(*args, **kwargs) 56 | after = datetime.datetime.now() 57 | elapsed = after - before 58 | ms = elapsed.total_seconds()*1000.0 59 | print "%s() took %.3f ms" % (fn.__name__, ms) 60 | return retVal 61 | return wrapper 62 | 63 | #--------------------------------------------------------------------- 64 | """ 65 | Type checking works like this: 66 | 67 | @typ(int, ret=int) 68 | def foo(x): 69 | return x*x 70 | 71 | """ 72 | 73 | def typeName(ty): 74 | """ Return the name of a type, e.g.: 75 | typeName(int) => 'int' 76 | typeName(Foo) => 'Foo' 77 | typeName((int,str)) => 'int or str' 78 | @param ty [type|tuple of type] 79 | @return [str] 80 | """ 81 | if isinstance(ty, tuple): 82 | return " or ".join(t.__name__ for t in ty) 83 | else: 84 | return ty.__name__ 85 | 86 | class typ: 87 | """ decorator to check a functions argument type """ 88 | 89 | def __init__(self, *argTypes, **retType): 90 | self.argTypes = argTypes 91 | if retType.has_key('ret'): 92 | self.ret = retType['ret'] 93 | else: 94 | self.ret = None 95 | 96 | 97 | def __call__(self, fn): 98 | """ return a new function that when called, checks 99 | the arguments before calling the original function. 100 | """ 101 | if not debugging: 102 | return fn 103 | isMethod = inspect.getargspec(fn).args[0] == 'self' 104 | @functools.wraps(fn) 105 | def wrapper(*args): 106 | if isMethod: 107 | checkArgs = args[1:] 108 | else: 109 | checkArgs = args 110 | # check number of args 111 | if len(checkArgs)=%d" 113 | % (fn.__name__, len(checkArgs), len(self.argTypes))) 114 | raise TypeError(msg) 115 | # check args 116 | for ix, arg in enumerate(checkArgs): 117 | sbType = self.argTypes[ix] # what the type should be 118 | if sbType!=None and not isinstance(arg, sbType): 119 | msg = ("calling %s(), arg[%d] had type of %s," 120 | " should be %s") % (fn.__name__, 121 | ix, 122 | type(arg).__name__, 123 | typeName(sbType)) 124 | raise TypeError(msg) 125 | retval = fn(*args) 126 | # check return type 127 | if self.ret!=None and not isinstance(retval, self.ret): 128 | msg = ("%s() returns type of %s," 129 | " should be %s") % (fn.__name__, 130 | type(retval).__name__, 131 | typeName(self.ret)) 132 | raise TypeError(msg) 133 | return retval 134 | return wrapper 135 | 136 | 137 | #--------------------------------------------------------------------- 138 | # print values 139 | 140 | def _prVarsSelf(cLocals, vn): 141 | selfOb = cLocals['self'] 142 | value = selfOb.__dict__[vn[5:]] 143 | r = " %s=%r" % (vn, value) 144 | return r 145 | 146 | def prvars(varNames =None): 147 | if not debugging: return 148 | if isinstance(varNames, str): 149 | vnList = varNames.split() 150 | caller = inspect.stack()[1] 151 | cLocals = caller[0].f_locals # local variables of caller 152 | #print cLocals 153 | fileLine = caller[2] 154 | functionName = caller[3] 155 | filename = caller[0].f_code.co_filename 156 | output = "%s():%d" % (functionName, fileLine) 157 | outputForSelf = " "*len(output) 158 | printAllSelf = False 159 | if varNames==None: 160 | for vn in sorted(cLocals.keys()): 161 | output += " %s=%r" %(vn, cLocals[vn]) 162 | if cLocals.has_key('self'): printAllSelf = True 163 | else: 164 | for vn in vnList: 165 | if vn.startswith("self."): 166 | output += _prVarsSelf(cLocals, vn) 167 | elif cLocals.has_key(vn): 168 | output += " %s=%r" %(vn, cLocals[vn]) 169 | if vn=='self': printAllSelf = True 170 | if printAllSelf: 171 | selfOb = cLocals['self'] 172 | for insVar in sorted(selfOb.__dict__.keys()): 173 | val = selfOb.__dict__[insVar] 174 | output += "\n" + outputForSelf + " self.%s=%r"%(insVar,val) 175 | sys.stderr.write(output + "\n") 176 | 177 | #--------------------------------------------------------------------- 178 | 179 | def pr(formatStr, *args): 180 | caller = inspect.stack()[1] 181 | cLocals = caller[0].f_locals # local variables of caller 182 | fileLine = caller[2] 183 | functionName = caller[3] 184 | 185 | if len(args)>0: 186 | s = formatStr % args 187 | else: 188 | s = formatStr 189 | t = "%s():%d: " % (functionName, fileLine) 190 | sys.stderr.write(t + s + "\n") 191 | 192 | def prNo(formatStr, *args): 193 | """ as pr() but with no line numbers prepended """ 194 | if len(args)>0: 195 | s = formatStr % args 196 | else: 197 | s = formatStr 198 | sys.stderr.write(s + "\n") 199 | 200 | #--------------------------------------------------------------------- 201 | 202 | def getCallerLocals(): 203 | """ 204 | Get the local variables for the function that called the function 205 | that called this function (i.e. two call stack levels back) 206 | @return [dict] 207 | """ 208 | caller2 = inspect.stack()[2] 209 | return caller2[0].f_locals 210 | 211 | def getCallerLocal(varName): 212 | """ 213 | Get a local variable for the function that called the function 214 | that called this function (i.e. two call stack levels back) 215 | @param varName [str] the name of the variable we want 216 | @return [dict] 217 | """ 218 | caller2 = inspect.stack()[2] 219 | return caller2[0].f_locals[varName] 220 | 221 | 222 | #--------------------------------------------------------------------- 223 | 224 | 225 | #end 226 | -------------------------------------------------------------------------------- /app/ulib/istream.py: -------------------------------------------------------------------------------- 1 | # istream.py 2 | 3 | """ 4 | input streams for Python 5 | 6 | History: 7 | 16-Dec-2006: implemented PeekStream:peekStr() 8 | 9 | 10-Apr-2007: added IStream:grabToString() 10 | 11 | 2-May-2007: added IStream:grabToBefore() 12 | 13 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 14 | Class hierarchy; 15 | 16 | IStream (ab) 17 | IFile -- a readable file 18 | PeekStream (ab) 19 | ScanString -- a scannable string 20 | FileWrapper 21 | 22 | """ 23 | 24 | import string 25 | 26 | debug = False # debugging this module? 27 | 28 | #--------------------------------------------------------------------- 29 | 30 | DIGITS = "0123456789" 31 | LETTERSU = string.ascii_letters + "_" 32 | IDENTCHARS = LETTERSU + string.digits 33 | 34 | def cin(c, s): 35 | """ is c in s? If c is empty, return False. 36 | @param c [string] = a character or '' 37 | @param s [string] = a string; c must match one char in s 38 | @return [bool] 39 | """ 40 | if c=='': return False 41 | return c in s 42 | 43 | def isDigit(c): 44 | return cin(c, DIGITS) 45 | 46 | def isLetterU(c): 47 | return cin(c, LETTERSU) 48 | 49 | def isIdentChar(c): 50 | return cin(c, IDENTCHARS) 51 | 52 | #--------------------------------------------------------------------- 53 | 54 | class IStream: 55 | 56 | #======================================================== 57 | # to be implemented by subclasses: 58 | 59 | def get(self): 60 | """ Get the next character in the stream 61 | @return [string] length 1; or '' if EOF 62 | """ 63 | raise NotImplementedError 64 | 65 | def eof(self): 66 | """ are we at the end of this stream? 67 | @return [bool] 68 | """ 69 | raise NotImplementedError 70 | 71 | #======================================================== 72 | 73 | def getChar(self): 74 | """ alias for get() """ 75 | return self.get() 76 | 77 | def getLine(self): 78 | """ Get the next line, including '\n' at end 79 | @return [string] 80 | """ 81 | if debug: 82 | print "*** in getLine() ***" 83 | line = "" 84 | while 1: 85 | nextChar = self.get() 86 | if debug: 87 | print "*** in getLine() 2. nextChar=%r ***" % (nextChar,) 88 | if nextChar == "": return line 89 | if debug: 90 | print "*** in getLine() 3. nextChar=%r ***" % (nextChar,) 91 | line += nextChar 92 | if nextChar == "\n": return line 93 | 94 | def getLines(self): 95 | """ Get all the lines 96 | @return [list of string] 97 | """ 98 | lines = [] 99 | while 1: 100 | line = self.getLine() 101 | if line == "": return lines 102 | lines += [line] 103 | 104 | def getAll(self): 105 | """ Get all the characters in the stream as a string 106 | @return [string] 107 | """ 108 | lines = self.getLines() 109 | return string.join(lines, "") 110 | 111 | def getChars(self, n =-1): 112 | """ Get (n) characters in the stream. If n<0, get the lot. 113 | @return [string] 114 | """ 115 | 116 | 117 | #--------------------------------------------------------------------- 118 | """ 119 | A wrapper round a file object 120 | 121 | Instance variables 122 | ~~~~~~~~~~~~~~~~~~ 123 | atEnd [bool] are we at the end of the file? 124 | f [file] the underlying file object 125 | """ 126 | 127 | class IFile(IStream): 128 | 129 | def __init__(self, file= None, filename= None): 130 | """ 131 | Create a new IFile. Either file or filename must be set. 132 | @param file [file] = a file object 133 | @param filename = a filename or pathname 134 | """ 135 | if file: 136 | self.f = file 137 | elif filename: 138 | pass 139 | else: 140 | msg = "Error in creating IFile; must include file or"\ 141 | " filename (file=%r, filename=%r)" % (file, filename) 142 | raise Exception(msg) 143 | self.atEnd = False 144 | 145 | def get(self): 146 | ch = self.f.read(1) 147 | if len(ch)==0: self.atEnd = True 148 | return ch 149 | 150 | def eof(self): 151 | return self.atEnd 152 | 153 | 154 | #--------------------------------------------------------------------- 155 | 156 | class PeekStream(IStream): 157 | 158 | #======================================================== 159 | # to be implemented by subclasses: 160 | 161 | def peek(self, lookahead=0): 162 | """ Returns a peek of one char. If lookahead==0, returns the 163 | next to be written in the stream. 164 | @param n [int] 165 | @return [string] length 1; or "" if no more chars 166 | at relevant position 167 | """ 168 | raise NotImplementedError 169 | 170 | #======================================================== 171 | 172 | def eof(self): 173 | return self.peekChar()=='' 174 | 175 | def peekChar(self, lookahead=0): 176 | """ alias for peek() """ 177 | return self.peek(lookahead) 178 | 179 | def yyypeekStr(self, n, lookahead=0): 180 | """ peekStr(n) returns a peek of the next (n) chars; however 181 | peekStr(n, f) returns a peek of the next (n) characters, 182 | starting from an offset of (f) characters ahead from the 183 | current position. 184 | @param n [int] 185 | @param lookahead [int] = where to start looking from 186 | @return [string] length n or shorter if no more characters 187 | """ 188 | 189 | def isNext(self, matchStr): 190 | """ Are the next chars are (matchStr)? 191 | @param matchStr [string] 192 | @return [bool] 193 | """ 194 | return self.peekStr(len(matchStr)) == matchStr 195 | 196 | def isNextWord(self): 197 | """ Is what follows a C-style identifier? 198 | @return [bool] 199 | """ 200 | ch = self.peek() 201 | return isLetterU(ch) 202 | 203 | def grabWord(self): 204 | """ grab a C-style identifier. Throw away characters until 205 | we're at the start of one, then greedily grab all of it. 206 | @return [string] = the identifier, or '' if couldn't get one 207 | """ 208 | while 1: 209 | if self.peek()=='': return '' 210 | if self.isNextWord(): break 211 | self.get() 212 | r = '' 213 | r = self.get() 214 | while isIdentChar(self.peek()): 215 | r += self.get() 216 | return r 217 | 218 | def isNextInt(self): 219 | """ Is what follows an integer? (ie optional '-' 220 | followed by 1 or more [0-9] 221 | @return [bool] 222 | """ 223 | c = self.peek() 224 | if c=='': return False 225 | if isDigit(c): return True 226 | if c=="-" and isDigit(self.peek(1)): return True 227 | return False 228 | 229 | def grabInt(self, notFound=None): 230 | """ Scan forward until an int is found, then return it. 231 | If no int found, return (notFound) 232 | @return [int] 233 | """ 234 | while not self.isNextInt(): 235 | c = self.get() 236 | if c == '': return notFound 237 | r = self.get() 238 | while isDigit(self.peek()): 239 | r += self.get() 240 | return int(r) 241 | 242 | 243 | def isNextSkip(self, matchStr): 244 | """ Are the next chars are (matchStr)? If so, skip them 245 | and return True. 246 | @param matchStr [string] 247 | @return [bool] 248 | """ 249 | lm = len(matchStr) 250 | isNext = (self.peekStr(lm) == matchStr) 251 | if isNext: self.getChars(lm) 252 | return isNext 253 | 254 | def skipPast(self, s): 255 | """ Keep consuming characters until the most recently 256 | consumed are the string (s) 257 | @param s [string] 258 | @return [bool] True if skipped any characters 259 | """ 260 | ls = len(s) 261 | if ls==0: return False 262 | while 1: 263 | if self.isNextSkip(s) or self.eof(): return True 264 | self.get() 265 | 266 | def grabToString(self, s): 267 | """ Keep reading characters until we either reach the end of 268 | the stream, or the most-recently-read characters are the string (s). 269 | If (s) is '', don't read any characters. 270 | Return all the characters read, including the (s) at the end. 271 | @param s [string] 272 | @return [string] = all the characters that have been grabbed 273 | """ 274 | if s=="": return "" 275 | charsRead = "" 276 | lens = len(s) 277 | while 1: 278 | if self.eof(): return charsRead 279 | charsRead += self.get() 280 | if len(charsRead)>= lens and charsRead[-lens:]==s: 281 | # we've just read (s), so quit 282 | return charsRead 283 | #//while 284 | 285 | def grabToBefore(self, s): 286 | """ Keep reading characters until we either reach the end of 287 | the stream, or the next-to-be-read characters are the string (s). 288 | Return the characters grabbed. If what's next in the stream is 289 | (s), return an empty string. 290 | @param s [string] = a string which must follow the characters 291 | to be grabbed 292 | @return [string] = the characters grabbed 293 | """ 294 | grabbed = "" 295 | while 1: 296 | if self.isNext(s): return grabbed 297 | if self.eof(): return grabbed 298 | grabbed += self.get() 299 | 300 | def isNextSkip_emptyLine(self): 301 | """ Are the next characters an empty line? 302 | If so, skip them and return True. If not, return False. 303 | An "empty line" means "\n\n". 304 | @return [bool] 305 | """ 306 | return self.isNextSkip("\n\n") 307 | 308 | def skipPastSet(self, chars): 309 | """ Keep consuming characters until the next char to be read 310 | isn't in the set (chars), 311 | skip past it. 312 | @param chars [string] 313 | @return [bool] True if skipped any characters 314 | """ 315 | r = False 316 | while 1: 317 | ch = self.peek() 318 | if ch=='': break 319 | if ch not in chars: break 320 | self.get() 321 | r = True 322 | return r 323 | 324 | 325 | #--------------------------------------------------------------------- 326 | 327 | class ScanString(PeekStream): 328 | 329 | def __init__(self, s): 330 | """ create a scannable string 331 | @param s [string] 332 | """ 333 | if debug and type(s)!=str: 334 | print "ScanString:__init__(%r) ***NOT A STRING***" % (s,) 335 | assert type(s)==str 336 | self.s = s 337 | self.at = 0 338 | 339 | #======================================================== 340 | # inherited from superclasses: 341 | 342 | def get(self): 343 | ch = self.s[self.at:self.at+1] 344 | self.at += 1 345 | return ch 346 | 347 | def getChars(self, n=-1): 348 | if n<0: 349 | r = self.s[self.at:] 350 | self.at = len(self.s) 351 | else: 352 | ixto = self.at + n 353 | r = self.s[self.at:ixto] 354 | self.at += n 355 | if self.at > len(self.s): self.at = len(self.s) 356 | return r 357 | 358 | def peek(self, lookahead=0): 359 | ix = self.at + lookahead 360 | result = self.s[ix:ix+1] 361 | if debug: 362 | print "ScanString:peek(%r) ix=%r result=%r"\ 363 | % (lookahead, ix, result) 364 | return result 365 | 366 | def peekStr(self, n, lookahead=0): 367 | """ peekStr(n) returns a peek of the next (n) chars; however 368 | peekStr(n, f) returns a peek of the next (n) characters, 369 | starting from an offset of (f) characters ahead from the 370 | current position. 371 | @param n [int] 372 | @param lookahead [int] = where to start looking from 373 | @return [string] length n or shorter if no more characters 374 | """ 375 | ixto = lookahead + self.at + n 376 | return self.s[lookahead + self.at : ixto] 377 | 378 | #======================================================== 379 | 380 | #--------------------------------------------------------------------- 381 | """ 382 | A wrapper around a file object (i.e. an object created with the Python 383 | built-in file() function) 384 | 385 | """ 386 | 387 | class FileWrapper(PeekStream): 388 | def __init__(self, file= None, filename= None): 389 | """ 390 | @param file [file] = a file object 391 | @param filename = a filename or pathname 392 | """ 393 | if file: 394 | self.f = file 395 | elif filename: 396 | pass 397 | else: 398 | msg = "Error in creating FileWrapper; must include file or"\ 399 | " filename (file=%r, filename=%r)" % (file, filename) 400 | raise Exception(msg) 401 | 402 | 403 | #======================================================== 404 | # inherited from superclasses 405 | 406 | 407 | def get(self): 408 | """ return the next character 409 | return [string] 410 | """ 411 | 412 | 413 | 414 | #======================================================== 415 | 416 | #--------------------------------------------------------------------- 417 | 418 | 419 | #end 420 | -------------------------------------------------------------------------------- /app/ulib/lintest.py: -------------------------------------------------------------------------------- 1 | # lintest.py = linear regression testing 2 | 3 | """*** 4 | linear testing framework 5 | 6 | Last altered: 20-Jan-2014 7 | 8 | History: 9 | 6-May-2004: created 10 | 11 | 4-Sep-2008: added new test: 12 | FileAssertionMixin:assertFileDoesNotExist(pathname) 13 | 14 | 17-Feb-2011: copied back to pylib. 15 | 16 | 31-Jul-2013: added new assertion: assertDirExists(), to check 17 | whether a directory exists. 18 | 19 | 31-Jul-2013: added 'require' facility so a TestCase can require other 20 | TestCases to be run before it. This is tested in . 21 | 22 | 16-Jan-2014 added assertApprox for approximate comparisons of f.p. 23 | numbers 24 | 25 | ***""" 26 | 27 | import sys, os.path, stat 28 | 29 | import butil, termcolours 30 | tc = termcolours.TermColours 31 | 32 | 33 | #--------------------------------------------------------------------- 34 | # globals 35 | 36 | debug = False 37 | 38 | assertionsPassed = 0 39 | functionsPassed = 0 40 | testCasesRun = [] 41 | 42 | #--------------------------------------------------------------------- 43 | 44 | class ColoursMixin: 45 | PASSED = tc.GREEN + "PASSED" + tc.NORMAL 46 | FAILED = tc.RED + "FAILED" + tc.NORMAL 47 | 48 | # colours for a line noting entry into a test method 49 | TEST_METHOD_LINE = tc.BLUE 50 | NORMAL = tc.NORMAL 51 | 52 | #--------------------------------------------------------------------- 53 | 54 | """*** 55 | Abstract superclass for tests 56 | 57 | Interface: 58 | ---------- 59 | 60 | Test instances should understand: 61 | run() - run all the tests in the test, and print results 62 | ***""" 63 | 64 | class Test(object): 65 | 66 | def __init__(self, name =""): 67 | self.name = name 68 | self.tests = [] 69 | self.parent = None 70 | 71 | def getFullName(self): 72 | fullName = self.getParentName() 73 | if len(fullName) > 0: 74 | fullName += " >> " 75 | fullName += self.name 76 | return fullName 77 | 78 | def getParentName(self): 79 | if self.parent: 80 | return self.parent.getFullName() 81 | else: 82 | return "" 83 | 84 | def printTestResults(self): 85 | p = "Passed %d assertions in %d test functions" \ 86 | % (assertionsPassed, functionsPassed) 87 | ptop = "*" * (len(p)+6) 88 | ptop = tc.GREEN + ptop + tc.NORMAL 89 | s2 = tc.GREEN + "**" + tc.NORMAL 90 | print "\n%s\n%s %s %s\n%s" % ( 91 | ptop, 92 | s2, p, s2, 93 | ptop) 94 | 95 | #--------------------------------------------------------------------- 96 | """*** 97 | A mixin that adds some assertions relating to files 98 | ***""" 99 | 100 | def fileExists(fn): 101 | """ Does a file exist? 102 | @param fn [string] = a filename or pathname 103 | @return [boolean] = True if (fn) is the filename of an existing file 104 | and it is readable. 105 | """ 106 | if debug: print "fileExists(%r)" % (fn,) 107 | fn = os.path.expanduser(fn) 108 | readable = os.access(fn, os.R_OK) 109 | # (if it doesn't exist, it can't be readable, so don't bother 110 | # testing that separately) 111 | if not readable: return 0 112 | # now test if it's a file 113 | mode = os.stat(fn).st_mode 114 | return stat.S_ISREG(mode) 115 | 116 | def dirExists(fn): 117 | """ Does a directory exist? 118 | @param fn [string] = a filename or pathname for a directory 119 | @return [boolean] = True if (fn) is the name of an existing directory 120 | and it is readable. 121 | """ 122 | if debug: print "fileExists(%r)" % (fn,) 123 | fn = os.path.expanduser(fn) 124 | readable = os.access(fn, os.R_OK) 125 | # (if it doesn't exist, it can't be readable, so don't bother 126 | # testing that separately) 127 | if not readable: return 0 128 | # now test if it's a directory 129 | mode = os.stat(fn).st_mode 130 | return stat.S_ISDIR(mode) 131 | 132 | class FileAssertionMixin(ColoursMixin): 133 | 134 | def assertFileExists(self, pan, comment=""): 135 | """ does file (pan) exist? 136 | @param pan [string] = a full pathname to a file 137 | """ 138 | ok = fileExists(pan) 139 | if ok: 140 | self.passedTest("%s; file <%s> exists" % (comment, pan)) 141 | else: 142 | msg = "FAILED: %s; file <%s> doesn't exist" % (comment, pan) 143 | raise AssertionError, msg 144 | 145 | def assertFileDoesNotExist(self, pan, comment=""): 146 | """ does file (pan) exist? It shouldn't. 147 | @param pan [string] = a full pathname to a file 148 | """ 149 | ok = not fileExists(pan) 150 | if ok: 151 | self.passedTest("%s; file <%s> correctly doesn't exist"\ 152 | % (comment, pan)) 153 | else: 154 | msg = "FAILED: %s; file <%s> exists, when it shouldn't"\ 155 | % (comment, pan) 156 | raise AssertionError, msg 157 | 158 | def assertDirExists(self, pan, comment=""): 159 | """ does directory (pan) exist? 160 | @param pan [string] = a full pathname to a file 161 | """ 162 | ok = dirExists(pan) 163 | if ok: 164 | self.passedTest("%s; directory <%s> exists" % (comment, pan)) 165 | else: 166 | msg = "FAILED: %s; directory <%s> doesn't exist" % (comment, pan) 167 | raise AssertionError, msg 168 | 169 | def assertFilesEqual(self, pan1, pan2, comment=""): 170 | """ Do two files contain the same data? 171 | 172 | (maybe we should use diff for long files?) 173 | 174 | @param pan1 [string] = filename or pathname for 1st file 175 | @param pan2 [string] = filename or pathname for 2nd file 176 | @param comment [string] 177 | """ 178 | data1 = butil.readFile(pan1) 179 | data2 = butil.readFile(pan2) 180 | if data1 == data2: 181 | self.passedTest("%s; files <%s> and <%s> contain the same data" 182 | % (comment, pan1, pan2)) 183 | else: 184 | msg = "FAILED: %s; files <%s>, <%s> contain different data"\ 185 | % (comment, pan1, pan2) 186 | raise AssertionError, msg 187 | 188 | def assertFileHasData(self, pan, data, comment=""): 189 | """ Are the contents of file (pan) equal to (data)? 190 | @param pan [string] = a full pathname to a file 191 | @param data [string] = what is supposed to be in the file 192 | """ 193 | dataInFile = butil.readFile(pan) 194 | difDisplay = "%r" % (dataInFile,) 195 | if len(dataInFile) > 80: 196 | difDisplay = "%d chars starting with %r" \ 197 | % (len(dataInFile), dataInFile[:80]) 198 | 199 | if dataInFile == data: 200 | self.passedTest("%s; file <%s> has data: %s" 201 | % (comment, pan, difDisplay)) 202 | else: 203 | dataDisplay = "%r" % (data,) 204 | if len(data) > 80: 205 | dataDisplay = "%d chars starting with %r" \ 206 | % (len(data), data[:80]) 207 | msg = "FAILED: %s; file <%s> contains: %s\nshould be: %s"\ 208 | % (comment, pan, difDisplay, dataDisplay) 209 | raise AssertionError, msg 210 | 211 | #======================================================== 212 | # utility function: 213 | #======================================================== 214 | 215 | def cmd(self, command): 216 | """ not a file assertion, but comes in incredibly useful """ 217 | print "CMD { %s }" % command 218 | os.system(command) 219 | 220 | #--------------------------------------------------------------------- 221 | 222 | """*** 223 | Superclass for the user's test cases 224 | 225 | ***""" 226 | 227 | class TestCase(Test, FileAssertionMixin, ColoursMixin): 228 | 229 | def passedTest(self, msg): 230 | global assertionsPassed 231 | assertionsPassed += 1 232 | print "%s - %s (%d)" % (msg, self.PASSED, assertionsPassed) 233 | 234 | #======================================================== 235 | # assertions 236 | #======================================================== 237 | 238 | def assertEqual(self, r, sb, comment=""): 239 | com2 = "" 240 | if comment: 241 | com2 = comment + "; " 242 | ok = (r == sb) 243 | if ok: 244 | msg = "%sr=%r" % (com2, r) 245 | self.passedTest(msg) 246 | else: 247 | msg = "FAILED: %sassertEqual\nr = %r\nsb= %r" % (com2, r, sb) 248 | raise AssertionError, msg 249 | assertSame = assertEqual 250 | 251 | def assertApprox(self, r, sb, comment=""): 252 | com2 = "" 253 | if comment: 254 | com2 = comment + "; " 255 | epsilon = 0.0001 256 | bigger = 1 + epsilon 257 | smaller = 1 - epsilon 258 | ok = (r == sb 259 | or sb*smaller < r < sb*bigger 260 | or sb*smaller > r > sb*bigger 261 | ) 262 | if ok: 263 | msg = "%sr=%r" % (com2, r) 264 | self.passedTest(msg) 265 | else: 266 | msg = "FAILED: %sassertApprox\nr = %r\nsb= %r" % (com2, r, sb) 267 | raise AssertionError, msg 268 | 269 | def assertNotEqual(self, r, snb, comment=""): 270 | com2 = "" 271 | if comment: 272 | com2 = comment + "; " 273 | ok = (r != snb) 274 | if ok: 275 | msg = "%sr=%r r!=%r" % (com2, r, snb) 276 | self.passedTest(msg) 277 | else: 278 | msg = "FAILED: %sassertNotEqual\nr=%r\n(should be different)"\ 279 | % (com2, r) 280 | raise AssertionError, msg 281 | 282 | def assertBool(self, bool, comment=""): 283 | if bool: 284 | self.passedTest("%s; true" % comment) 285 | else: 286 | raise AssertionError, "Failed: %s" % comment 287 | assertTrue = assertBool 288 | assert_ = assertBool 289 | failUnless = assertBool 290 | 291 | def assertFalse(self, bool, comment=""): 292 | if not bool: 293 | self.passedTest("%s; correctly false" % comment) 294 | else: 295 | raise AssertionError, "Failed: %s" % comment 296 | failIf = assertFalse 297 | 298 | def failed(self, comment=""): 299 | # failed a test 300 | raise AssertionError, "Failed: %s" % comment 301 | fail = failed 302 | 303 | def passed(self, comment=""): 304 | # passed a test 305 | self.passedTest("%s; passed" % comment) 306 | 307 | #======================================================== 308 | 309 | #def setUpAll(self): pass 310 | #def setUp(self): pass 311 | #def tearDown(self): pass 312 | #def tearDownAll(self): pass 313 | 314 | #======================================================== 315 | # running tests 316 | #======================================================== 317 | 318 | def run(self, parent =None): 319 | global functionsPassed, testCasesRun 320 | self.optCR = "\n" 321 | if parent: self.parent = parent 322 | self._runRequirements(parent) 323 | testCasesRun.append(self.__class__) 324 | 325 | #self.parent = parent 326 | kn = self.__class__.__name__ 327 | tests = self.getTests() 328 | 329 | self.doRun("setUpAll") 330 | for test in tests: 331 | self.doRun("setUp") 332 | kn = self.__class__.__name__ 333 | funName = test.func_code.co_name 334 | funLineNum = test.func_code.co_firstlineno 335 | print "%s%s=== %s%s.%s:%d ===%s" \ 336 | % (self.optCR, 337 | self.TEST_METHOD_LINE, 338 | self._pnTxt(), kn, funName, 339 | funLineNum, 340 | self.NORMAL) 341 | test(self) 342 | functionsPassed += 1 343 | self.doRun("tearDown") 344 | self.optCR = "\n" 345 | self.doRun("tearDownAll") 346 | if not self.parent: 347 | self.printTestResults() 348 | 349 | def _pnTxt(self): 350 | pn = self.getParentName() 351 | if len(pn)>0: pn += " >> " 352 | return pn 353 | 354 | def canRun(self, methodName): 355 | b = hasattr(self, methodName) 356 | return b 357 | 358 | def doRun(self, methodName): 359 | if self.canRun(methodName): 360 | print "%s%s@@@ %s.%s()%s" % ( 361 | self.optCR, 362 | self.TEST_METHOD_LINE, 363 | self.__class__.__name__, methodName, 364 | self.NORMAL) 365 | exec("self.%s()" % methodName) 366 | self.optCR = "" 367 | 368 | def getTests(self): 369 | """ Return all the test functions in this class """ 370 | if debug: print "getTests() dict=%r" % self.__class__.__dict__ 371 | 372 | tests = [] 373 | for k,v in self.__class__.__dict__.items(): 374 | if k[:5] == "test_": 375 | vfc = v.func_code 376 | #print "test function: %s (%d)" % (vfc.co_name, vfc.co_firstlineno) 377 | tests.append(v) 378 | tests.sort(compare_funs) 379 | if debug: print "getTests() => %r" % (tests,) 380 | return tests 381 | 382 | #======================================================= 383 | # running requirements 384 | #======================================================= 385 | 386 | def _runRequirements(self, parent): 387 | """ Does this TestCase need any other test cases to be run 388 | before it can be run? If so, run them 389 | """ 390 | global testCasesRun 391 | reqs = self._getRequirements() 392 | #print "_runRequirements() reqs=%r" % (reqs,) 393 | for req in reqs: 394 | if req not in testCasesRun: 395 | print "%s requires %s, running it first..."\ 396 | % (self.__class__.__name__, req.__name__) 397 | reqInstance = req() 398 | self.name = "(%s)" % self.__class__.__name__ 399 | reqInstance.run(self) 400 | #//for 401 | 402 | def _getRequirements(self): 403 | """ get the requirements of this TestCase, 404 | which are stored in the requires class variable. 405 | If it isn't a list, make it one. 406 | @return [list of TestCase] 407 | """ 408 | try: 409 | r = self.requires 410 | except: 411 | r = [] 412 | if isinstance(r, tuple): 413 | r = list(r) 414 | elif not isinstance(r, list): 415 | r = [r] 416 | return r 417 | 418 | 419 | #--------------------------------------------------------------------- 420 | # comparison function for functions 421 | 422 | def compare_funs(f1, f2): 423 | f1c = getFunCollate(f1) 424 | f2c = getFunCollate(f2) 425 | result = cmp(f1c, f2c) 426 | return result 427 | 428 | def getFunCollate(f): 429 | ffc = f.func_code 430 | r = "%05d %s" % (ffc.co_firstlineno, ffc.co_name) 431 | return r 432 | 433 | #--------------------------------------------------------------------- 434 | 435 | """*** 436 | A group of TestCases (or TestGroups) 437 | 438 | Interface: 439 | ---------- 440 | 441 | TestGroup 442 | 443 | addCase(TestCaseSubclass) 444 | addTest(aTestGroup) 445 | add(aModule) - do later? 446 | add(list of these) 447 | 448 | ***""" 449 | 450 | class TestGroup(Test): 451 | 452 | def __init__(self, name =None): 453 | Test.__init__(self, name) 454 | self.name = name 455 | self.tests = [] 456 | self.parent = None 457 | 458 | if self.name == None: 459 | # use defualt value for name, which is the filenamer of the 460 | # calling module 461 | frame = sys._getframe(1) 462 | fn = frame.f_code.co_filename 463 | shortFn = os.path.basename(fn) 464 | root, extension = os.path.splitext(shortFn) 465 | self.name = root 466 | if debug: print "TestGroup is taking name %r" % self.name 467 | 468 | #======================================================== 469 | # running tests 470 | #======================================================== 471 | 472 | def run(self, parent =None): 473 | self.parent = parent 474 | for test in self.tests: 475 | test.run(self) 476 | if not self.parent: 477 | self.printTestResults() 478 | 479 | #======================================================== 480 | # adding tests 481 | #======================================================== 482 | 483 | def addTest(self, aTest): 484 | """add a test to the group. 485 | @param aTest [Test] 486 | """ 487 | self.tests.append(aTest) 488 | 489 | 490 | def addCase(self, testCaseSubclass): 491 | """ add a subclass of TestCase to the group. 492 | @param testCaseSubclass [class] 493 | """ 494 | if debug: print "... adding test case %s" % testCaseSubclass.__name__ 495 | inst = testCaseSubclass() 496 | self.addTest(inst) 497 | 498 | def add(self, *args): 499 | """ add some Tests of TestCase subclasses to this group """ 500 | for arg in args: 501 | if isinstance(arg, Test): 502 | self.addTest(arg) 503 | else: 504 | self.addCase(arg) 505 | 506 | 507 | #--------------------------------------------------------------------- 508 | 509 | #end 510 | -------------------------------------------------------------------------------- /app/ulib/termcolours.py: -------------------------------------------------------------------------------- 1 | # termcolours.py 2 | 3 | """*** 4 | Optional support for printing to the terminal in multiple colurs 5 | 6 | ***""" 7 | 8 | #--------------------------------------------------------------------- 9 | 10 | class TermColours: 11 | BLACK = chr(27) + "[0;30m" 12 | RED = chr(27) + "[0;31m" 13 | GREEN = chr(27) + "[0;32m" 14 | BLUE = chr(27) + "[0;34m" 15 | MAGENTA = chr(27) + "[0;35m" 16 | 17 | RED_ON_GREY = chr(27) + "[47;31m" 18 | LRED_ON_GREY = chr(27) + "[1;47;31m" 19 | BLACK_ON_RED = chr(27) + "[41;30m" 20 | BLACK_ON_GREY = chr(27) + "[47;30m" 21 | BLUE_ON_GREY = chr(27) + "[47;34m" 22 | YELLOW_ON_RED = chr(27) + "[1;47;30m" 23 | GREY_ON_WHITE = chr(27) + "[48;37m" 24 | DGREY_ON_WHITE = chr(27) + "[1;48;30m" 25 | LCYAN_ON_BLUE = chr(27) + "[1;44;36m" 26 | 27 | NORMAL = chr(27) + "[0m" 28 | BOLD = chr(27) + "[1m" 29 | FAINT = chr(27) + "[2m" 30 | UNDERLINE = chr(27) + "[4m" 31 | 32 | 33 | def prColours(): 34 | bgCol = range(30,50) 35 | fgCol = range(30,50) 36 | for bg in bgCol: 37 | for fg in fgCol: 38 | seq = "[%d;%dm" % (bg, fg) 39 | escSeq = chr(27) + seq + seq + chr(27) + "[0;30m" 40 | print escSeq, 41 | if fg==39: print 42 | print 43 | for i in range(0,8): 44 | seq = "[%dm" % i 45 | print TermColours.NORMAL + chr(27) + seq + " " + seq 46 | print TermColours.NORMAL 47 | 48 | class NullColours: 49 | BLACK = '' 50 | RED = '' 51 | GREEN = '' 52 | BLUE = '' 53 | MAGENTA = '' 54 | RED_ON_GREY = '' 55 | LRED_ON_GREY = '' 56 | BLACK_ON_RED = '' 57 | BLACK_ON_GREY = '' 58 | BLUE_ON_GREY = '' 59 | YELLOW_ON_RED = '' 60 | GREY_ON_WHITE = '' 61 | DGREY_ON_WHITE = '' 62 | LCYAN_ON_BLUE = '' 63 | 64 | tc = TermColours() 65 | #tc = NullColours() 66 | 67 | class Markup(TermColours): 68 | NORMAL = tc.BLACK 69 | SM = tc.BLUE 70 | ADDR = tc.GREEN 71 | #ADDR = chr(27) + "[46;34m" 72 | #ADDR = tc.BLUE_ON_GREY 73 | TREE = tc.BLACK 74 | TREE_TITLE = tc.BLUE_ON_GREY 75 | LLSC = tc.BLUE_ON_GREY 76 | #LLSC = tc.BLUE_ON_GREY 77 | LLSC_LVAL = tc.MAGENTA 78 | 79 | #--------------------------------------------------------------------- 80 | 81 | if __name__=="__main__": 82 | mu = Markup() 83 | print mu.NORMAL + "normal " + mu.SM + "#(1 2 55 $nil) " + mu.ADDR + "addr" 84 | print "red or black???" 85 | prColours() 86 | print TermColours.BOLD + "should be bold" 87 | 88 | print TermColours.NORMAL + "back to normal" 89 | 90 | 91 | #end 92 | -------------------------------------------------------------------------------- /app/wiki.py: -------------------------------------------------------------------------------- 1 | # wiki.py 2 | 3 | import os.path, re, math, inspect, datetime, sys 4 | 5 | from flask import request, redirect, Response 6 | 7 | import markdown 8 | from markdown.extensions.toc import TocExtension 9 | 10 | from ulib import butil 11 | from ulib.butil import form 12 | from ulib.debugdec import prvars, pr, printargs 13 | 14 | import config 15 | import allpages 16 | from allpages import * 17 | 18 | 19 | #--------------------------------------------------------------------- 20 | # debugging 21 | 22 | def prt(formatStr="", *args): 23 | """ For debugging -- print a message, prepended with timestamp, function, 24 | line number. 25 | Uses old-style '%' format strings. 26 | @param formatStr::str 27 | @param args::[] 28 | """ 29 | now = datetime.datetime.now() 30 | nowStr = now.strftime("%H:%M:%S.%f") 31 | caller = inspect.stack()[1] 32 | fileLine = caller[2] 33 | functionName = caller[3] 34 | 35 | if len(args)>0: 36 | s = formatStr % args 37 | else: 38 | s = formatStr 39 | t = "%s %s():%d: " % (nowStr, functionName, fileLine) 40 | sys.stderr.write(t + s + "\n") 41 | 42 | #--------------------------------------------------------------------- 43 | 44 | @app.route("//w/") 45 | def wikiPageEmptyPath(siteName): 46 | return wikiPage(siteName, "") 47 | 48 | #--------------------------------------------------------------------- 49 | 50 | @app.route("//w/") 51 | def wikiPage(siteName, pathName): 52 | mimeType = getMimeType(pathName) 53 | if mimeType: 54 | pan = getDirPan(siteName, pathName) 55 | data = open(pan).read() 56 | return Response(data, mimetype=mimeType) 57 | 58 | if pathName=="" or pathName[-1:]=="/": 59 | tem = jinjaEnv.get_template("wiki_index.html") 60 | title, contents = getIndex(siteName, pathName) 61 | else: 62 | tem = jinjaEnv.get_template("wiki_page.html") 63 | title, contents = getArticleBody(siteName, pathName) 64 | 65 | h = tem.render( 66 | title = title, 67 | siteName = siteName, 68 | pathName = pathName, 69 | nav2 = locationSitePath(siteName, pathName), 70 | wikiText = contents, 71 | ) 72 | return h 73 | 74 | MIME_TYPES = [ 75 | ('pdf', 'application/pdf'), 76 | ('gif', 'image/gif'), 77 | ('png', 'image/png'), 78 | ('jpg', 'image/jpeg'), 79 | ('jpeg', 'image/jpeg'), 80 | ('xls', 'application/vnd.ms-excel'), 81 | ('xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'), 82 | ('xlst', 'application/vnd.openxmlformats-officedocument.spreadsheetml.template'), 83 | ] 84 | 85 | def getMimeType(pathName): 86 | """ Get the mime type of a pathname 87 | @param pathName::str 88 | @return::str containing mime type, or "" if none. 89 | """ 90 | pnl = pathName.lower() 91 | for ext, mt in MIME_TYPES: 92 | ext2 = "." + ext 93 | if pnl[-len(ext2):]==ext2: 94 | return mt 95 | #//for 96 | return "" 97 | 98 | #--------------------------------------------------------------------- 99 | 100 | @app.route("//wikiedit/", methods=['POST', 'GET']) 101 | def wikiedit(siteName, pathName): 102 | prt("siteName=%r pathName=%r", siteName, pathName) 103 | tem = jinjaEnv.get_template("wikiedit.html") 104 | 105 | if pathName=="" or pathName[-1:]=="/": 106 | # can't edit directories 107 | return redirect("/{siteName}/w/{pathName}".format( 108 | siteName = siteName, 109 | pathName = pathName, 110 | ), 301) 111 | else: 112 | source = getArticleSource(siteName, pathName) 113 | if source == "": 114 | source = "# " + pathName + "\n" 115 | if request.method=='POST': 116 | if request.form['delete'] == "1": 117 | # delete this article 118 | deleteArticle(siteName, pathName) 119 | articleDirectory = getArticleDirname(pathName) 120 | return redirect("/{siteName}/w/{pathName}".format( 121 | siteName = siteName, 122 | pathName = articleDirectory, 123 | ), 303) 124 | else: 125 | newSource = request.form['source'] 126 | prvars("newSource") 127 | saveArticleSource(siteName, pathName, newSource) 128 | return redirect("/{siteName}/w/{pathName}".format( 129 | siteName = siteName, 130 | pathName = pathName, 131 | ), 303) 132 | #//if 133 | title = pathName 134 | 135 | h = tem.render( 136 | title = title, 137 | siteName = siteName, 138 | pathName = pathName, 139 | nav2 = locationSitePath(siteName, pathName), 140 | source = source, 141 | ) 142 | prt("response length %d", len(h)) 143 | return h 144 | 145 | #--------------------------------------------------------------------- 146 | 147 | def pathJoin(sp, ix): 148 | return "/".join(sp[0:ix+1]) 149 | 150 | def locationSitePath(siteName, pathName): 151 | """ return html containing links for a wiki path 152 | @param siteName::str 153 | @param pathName::str 154 | @return::str, containing HTML 155 | """ 156 | 157 | useForDir = "" 158 | #useForDir = "" 159 | #useForDir = "/" 160 | sp = pathName.split("/") 161 | h = ("" 162 | "" 163 | "{siteName}").format( 164 | siteName=siteName) 165 | h += ("\n%s" 166 | % (siteName, useForDir)) 167 | #prvars("pathName sp") 168 | for ix, part in enumerate(sp): 169 | item = ("%s" 170 | %(siteName, pathJoin(sp,ix), part)) 171 | if ix%s" 173 | %(siteName, pathJoin(sp,ix), useForDir)) 174 | h += item 175 | #//for 176 | h += "" 177 | return h 178 | 179 | def getArticleDirname(pathName): 180 | """ move up to an article's directory """ 181 | sp = pathName.split("/") 182 | upOne = "/".join(sp[:-1]) + "/" 183 | return upOne 184 | 185 | def deleteArticle(siteName, pathName): 186 | """ delete an article """ 187 | pan = getDirPan(siteName, pathName) 188 | if pan: 189 | os.remove(pan + ".md") 190 | 191 | #--------------------------------------------------------------------- 192 | 193 | markdownProcessor = markdown.Markdown(['extra', 194 | 'sane_lists', 195 | 'toc', 196 | 'codehilite(guess_lang=False)', 197 | #'wikilinks(base_url=,end_url=)', 198 | ]) 199 | 200 | def md(s): 201 | """ Convert markdown to html 202 | 203 | Uses the Python Markdown library to do this. 204 | See: 205 | """ 206 | h = markdownProcessor.convert(s) 207 | return h 208 | 209 | def convertQuickLinks(s): 210 | """ Converts [[xxx]] -> [xxx](xxx) 211 | NB: we are no longer using this, we're using the wikilinks 212 | extension instead. 213 | 214 | @param s [str] containing markdown source 215 | @return [str] 216 | """ 217 | QUICKLINK_RE = r"\[\[([A-Za-z0-9_ -.]+)\]\]" 218 | REPLACE_WITH = r"[\1](\1)" 219 | r = re.sub(QUICKLINK_RE, REPLACE_WITH, s) 220 | return r 221 | 222 | 223 | def getDirPan(siteName, pathName): 224 | """ return the pathname for a directory 225 | @param siteName::str = the site name 226 | @param pathName::str = the pathname within the site 227 | @return::str = the full pathname to the directory (which may or may 228 | not exist). If the site doesn't exist, returns "". 229 | """ 230 | if not siteName: return "" 231 | for stub in config.SITE_STUBS: 232 | _, dirs = butil.getFilesDirs(stub) 233 | if siteName in dirs: 234 | return butil.join(stub, siteName, pathName) 235 | #//for 236 | return "" 237 | 238 | def saveArticleSource(siteName, pathName, source): 239 | #pr("saving article %s:[%s] -----BODY:-----\n%s\n-----END-----", 240 | # siteName, pathName, source) 241 | articlePan = getArticlePan(siteName, pathName) 242 | butil.writeFileUtf8(articlePan, source) 243 | 244 | def getArticleSource(siteName, pathName): 245 | articlePan = getArticlePan(siteName, pathName) 246 | if butil.fileExists(articlePan): 247 | src = butil.readFileUtf8(articlePan) 248 | return src 249 | else: 250 | return "" 251 | 252 | def getArticleBody(siteName, pathName): 253 | """ given an article name, return the body of the article. 254 | @return ::(str,str) =title,html 255 | """ 256 | articlePan = getArticlePan(siteName, pathName) 257 | #prvars() 258 | if butil.fileExists(articlePan): 259 | src = butil.readFileUtf8(articlePan) 260 | src = convertQuickLinks(src) 261 | contents = md(src) 262 | return pathName, contents 263 | else: 264 | h = form("

({pathName} does not exist; " 265 | "create it)

\n", 266 | siteName = htmlEscape(siteName), 267 | pathName = htmlEscape(pathName)) 268 | return (pathName, h) 269 | 270 | def getArticlePan(siteName, pathName): 271 | """ return the pathname for an article 272 | @param siteName::str = the site name 273 | @param pathName::str = the pathname within the site 274 | @return::str = the full pathname to the article (which may or may 275 | not exist). If the site doesn't exist, returns "". 276 | """ 277 | #prvars("siteName pathName") 278 | if not siteName: return "" 279 | for stub in config.SITE_STUBS: 280 | _, dirs = butil.getFilesDirs(stub) 281 | if siteName in dirs: 282 | return getArticlePan2(stub, siteName, pathName) 283 | #return butil.join(stub, siteName, pathName + ".md") 284 | #//for 285 | return "" 286 | 287 | def getArticlePan2(stub, siteName, pathName): 288 | """ return the pathname for an article, given the stub of the directory 289 | hierarchy to get it from. 290 | @param stub::str = the leftmost part of the pathname, to just before 291 | the siteName, e.g.: 292 | "/home/someuser/siteboxdata/sites" 293 | @param siteName::str = the site name 294 | @param pathName::str = the pathname within the site 295 | @return::str = the full pathname to the article (which may or may 296 | not exist). If the site doesn't exist, returns "". 297 | """ 298 | pathNameParts = pathName.split("/") 299 | #prvars("pathNameParts") 300 | pnLastPart = pathNameParts[-1] 301 | normLP = normArticleName(pnLastPart) 302 | pathName2 = "/".join(pathNameParts[:-1] + [normLP]) 303 | #prvars("normLP pathName2") 304 | 305 | useDir = butil.join(stub, siteName, "/".join(pathNameParts[:-1])) 306 | if articleExists(useDir, normLP): 307 | return butil.join(useDir, normLP + ".md") 308 | 309 | # article doesn't exist under the normalised name, look elsewhere: 310 | articleNames = getArticleFilesWithoutExt(useDir) 311 | for an in articleNames: 312 | nan = normArticleName(an) 313 | #prvars("an nan") 314 | if nan==normLP: 315 | return butil.join(useDir, an + ".md") 316 | #//for 317 | 318 | # couldn't find it elsewhere, use the normalised name 319 | return butil.join(useDir, normLP + ".md") 320 | 321 | pn = butil.join(stub, siteName, pathName2 + ".md") 322 | prvars("pn") 323 | return pn 324 | 325 | def articleExists(d, an): 326 | """ 327 | @param d::str = a full path to a directory 328 | @param an::str = a filename within that directory, but without the 329 | ".md" extension 330 | @return::bool = whether the article (an) exists 331 | """ 332 | pan = butil.join(d, an + ".md") 333 | return butil.fileExists(pan) 334 | 335 | 336 | def getArticleFilesWithoutExt(d): 337 | """ 338 | @param d::str = a full path to a directory 339 | @return::[str] where each string is an article in the directory without 340 | the ".md" extension 341 | """ 342 | fns, _ = butil.getFilesDirs(d) 343 | arts = sorted([fn[:-3] 344 | for fn in fns 345 | if fn[-3:]==".md" and not fn[:1]=="~"]) 346 | return arts 347 | 348 | 349 | def getIndex(siteName, pathName): 350 | """ get an index of a directory. 351 | @param siteName::str 352 | @param pathName::str 353 | @return::(str,str) =title,html 354 | """ 355 | def isArticle(fn): 356 | """ is a filename an article? """ 357 | return (fn[-3:]==".md" and not fn[:1]=="~") 358 | 359 | 360 | if pathName[-1:] == "/": 361 | uPath = pathName[:-1] 362 | else: 363 | uPath = pathName 364 | dirPan = getDirPan(siteName, uPath) 365 | #prvars() 366 | if not os.path.isdir(dirPan): 367 | h = "

Directory {} does not exist.

\n".format(pathName) 368 | return h 369 | 370 | fns, dirs = butil.getFilesDirs(dirPan) 371 | dirs = [d for d in dirs if d[:1] != "."] 372 | arts = sorted([fn[:-3] 373 | for fn in fns 374 | if isArticle(fn)]) 375 | nonArticles = sorted([fn 376 | for fn in fns 377 | if not isArticle(fn)]) 378 | dirs = sorted(dirs) 379 | h = ("

Index of articles in " 380 | " /{}

\n").format(pathName) 381 | items = [] 382 | nonArticleItems = [] 383 | if arts: 384 | for fn in arts: 385 | text = getTitle(butil.join(dirPan, fn+".md")) 386 | if text==fn: 387 | text = "" 388 | else: 389 | text = " - " + text 390 | 391 | if fn=="home": 392 | item = form("" 393 | "" 394 | " {fn}{text}", 395 | fn = fn, 396 | text = text) 397 | else: 398 | item = form("" 399 | " {fn}{text}", 400 | fn = fn, 401 | text = text) 402 | 403 | items.append(item) 404 | #//for 405 | h += bsColumns(items, 3) 406 | if nonArticles: 407 | for fn in nonArticles: 408 | hf = form("" 409 | " " 410 | "{fn}", 411 | fn = fn) 412 | if hasImageExtension(fn): 413 | hf += form("
\n" 414 | "" 415 | "", 416 | fn = fn) 417 | nonArticleItems.append(hf) 418 | #//for 419 | h += "

Other files

\n" + bsColumns(nonArticleItems, 3) 420 | if dirs: 421 | dirItems = [] 422 | for d in dirs: 423 | dirItems.append((" " 424 | "{text}").format( 425 | d = d, 426 | text = d, 427 | )) 428 | #//for 429 | h += "

Folders

\n" + bsColumns(dirItems, 3) 430 | 431 | title = "Index of {}".format(pathName) 432 | return title, h 433 | 434 | def getTitle(pan): 435 | """ get the title of an article 436 | @param pan [str] full pathname to the article 437 | """ 438 | src = butil.readFile(pan).decode('utf-8', 'ignore') 439 | lines = src.split("\n") 440 | if len(lines)==0: return "" 441 | t = md(convertQuickLinks(lines[0].strip(" #"))) 442 | if t.startswith("

"): t = t[3:] 443 | if t.endswith("

"): t = t[:-4] 444 | return t 445 | 446 | 447 | #--------------------------------------------------------------------- 448 | 449 | def bsColumns(hs, numColumns, linSize='md'): 450 | """ Bootstrap multiple columns 451 | @param hs::[str] = each string contains html 452 | @param numColumns::int = number of columns. values are 2|3|4|6. 453 | @param linSize::str = linearize on size. Linearize means revert to a 454 | one-column setup when screen width gets below a certain size. Allowed 455 | values are: 456 | 'xs' = never linearize 457 | 'sm' = on <768 pixels 458 | 'md' = on <992 pixels 459 | 'lg' = on <1200 pixels 460 | @return::str containing html 461 | """ 462 | if numColumns not in (2,3,4,6) or len(hs)<2*numColumns: 463 | h = ("""
464 |
465 | """ 466 | + "
\n".join(hs) 467 | + "
\n") 468 | return h 469 | columnClass = "col-{}-{}".format(linSize, 12/numColumns) 470 | itemsPerRow = int(math.ceil(len(hs)*1.0 / numColumns)) 471 | 472 | h = "
\n" 473 | for rowIx in range(numColumns): 474 | f = itemsPerRow * rowIx 475 | useHs = hs[f:f+itemsPerRow] 476 | h += form("""
477 | {elements} 478 |
479 | """, 480 | columnClass = columnClass, 481 | elements = "
\n".join(useHs)) 482 | #//for row 483 | h += "
\n" 484 | return h 485 | 486 | def hasImageExtension(fn): 487 | """ Does a filename have an extension indicating it's an image? 488 | @param fn::str = the filename 489 | @return::bool 490 | """ 491 | root, ext = os.path.splitext(fn) 492 | if ext[:1] != ".": return False # no extension, not an image 493 | 494 | IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif'] 495 | ext2 = ext[1:].lower() 496 | if ext2 in IMAGE_EXTENSIONS: 497 | return True 498 | return False 499 | 500 | 501 | 502 | #--------------------------------------------------------------------- 503 | # functions for nirmalising wiki page names 504 | 505 | def normArticleName(an): 506 | """ Normalise an article name e.g. "Hello" -> "hello" 507 | Characters [a-z0-9-] are passed through as is 508 | Characters [A-Z] are converted to lower case 509 | Any group of 1 or more other characters is replaced by a single "_" 510 | After this, beginning/ending "_" are removed. 511 | 512 | @param an::str = article name gtrom http request 513 | @return::str = normalised filename to look for 514 | """ 515 | PASS_THROUGH = "abcdefghijklmnopqrstuvwxyz0123456789-" 516 | TO_LOWER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 517 | r = "" 518 | for ch in an: 519 | if ch in PASS_THROUGH: 520 | r += ch 521 | elif ch in TO_LOWER: 522 | r += ch.lower() 523 | else: 524 | if r[-1:] != "_": 525 | r += "_" 526 | #//for 527 | r2 = r.strip("_") 528 | return r2 529 | 530 | 531 | 532 | #--------------------------------------------------------------------- 533 | 534 | #end 535 | -------------------------------------------------------------------------------- /data/catwiki/CatWiki enhancements to Markdown.md: -------------------------------------------------------------------------------- 1 | # CatWiki enhancements to Markdown 2 | 3 | [CatWiki](home) uses the [Python-Markdown](https://pythonhosted.org/Markdown/) implementation of [[Markdown]], with the following extensions: 4 | 5 | * [extra](https://pythonhosted.org/Markdown/extensions/extra.html) -- various improvements 6 | * [sane_lists](https://pythonhosted.org/Markdown/extensions/sane_lists.html) -- alters the behaviour of the Markdown List syntax to be less surprising 7 | * [toc](https://pythonhosted.org/Markdown/extensions/toc.html) -- allows for a table of contents 8 | * [codehilite](https://pythonhosted.org/Markdown/extensions/code_hilite.html) -- highlights code using [Pygments](http://pygments.org/) 9 | 10 | ## MediaWiki-style links 11 | 12 | In addition, CatWiki also allows MediaWiki-style links, i.e. starting with `[[` and ending with `]]`; within these links, allowed characters are letters (`A-Za-z`), digits (`0-9`), spaces, underlines (`_`), hyphens (`-`) and periods (`.`). 13 | 14 | Articles names in MediaWiki-style links can use different capitalisation and will link to the same article, E.g. consider this sentence: 15 | 16 | > There is a [[FAQ]] or [[faq]]. 17 | 18 | Both links go to the same article. 19 | 20 | 21 | ## See also 22 | 23 | * [[Markdown]] 24 | -------------------------------------------------------------------------------- /data/catwiki/Credits.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | 3 | Sitebox was written in [Python](https://www.python.org/) using the [Flask](http://flask.pocoo.org/) web framework. 4 | 5 | It uses the [ Font Awesome](http://fortawesome.github.io/Font-Awesome/) icon set. 6 | 7 | The Cat Icon was designed by [Martin Libreton](http://thenounproject.com/Martin%20LEBRETON/) and is licenced under the Creative Commons – Attribution (CC BY 3.0) licence. It is available from the [Noun Project](http://thenounproject.com/term/cat/18061/). 8 | 9 | -------------------------------------------------------------------------------- /data/catwiki/Editing toolbar.md: -------------------------------------------------------------------------------- 1 | # The Editing Toolbar 2 | 3 | Above the text entry area is the editing toolbar. It looks like this: 4 | 5 | >   6 |   7 |   8 |   9 |   10 |   11 |   12 |   13 |   14 |   15 |   16 | mo 17 | 18 | You use the editing toolbar by optionally selecting some text and then clicking on the relevant button. This puts Markdown markup around the selected text, to perform the relevant function. 19 | 20 | ## Text formatting tools: 21 | 22 | These tools allow you to format text. The tools are: 23 | 24 | Tool | Selected text | Changes to | Looks like | Notes 25 | -----| ------------- | ---------- | ---------- | ----- 26 | | `abc` | `**abc**` | **abc** | bold type 27 | | `abc` | `*abc*` | *abc* | italic type 28 | | `abc` | `abc` | abc | strikethrough 29 | | `abc` | `abc` | abc | superscript - only middle character selected 30 | | `H2O` | `H2O` | H2O | subscript - only middle character selected 31 | 32 | ## Tools for adding links to content: 33 | 34 | These tools allow you to specify a hyperlink or image. 35 | 36 | Tool | Selected text | Changes to | Looks like | Notes 37 | -----| ------------- | ---------- | ---------- | ----- 38 | | `abc` | `[abc]()` | [abc]() | link, url left blank 39 | | `abc` | `![abc](img)` | ![abc](img) | image, "img" is placeholder for image URL 40 | 41 | The hyperlink tool ( ) uses the linked text as the displayed text for a link. The user has to manually add the URL within the `()`. 42 | 43 | The image tool ( ) uses the linked text as the alt text for the image. The user has to replace the `img` within `(img)` with the actual 44 | URL for the image. 45 | 46 | ## The Table tool: 47 | 48 | The table tool ( ) inserts a table with 3 columns and 2 rows, which can then be further edited: 49 | ``` 50 | Head 1 | Head 2 | Head 3 51 | ------ | ------ | ------ 52 | cell 1 | cell 2 | cell 3 53 | cell 4 | cell 5 | cell 6 54 | ``` 55 | 56 | The table tool helps you if you can't remember the exact formatting for tables. The table it produces looks like this: 57 | 58 | Head 1 | Head 2 | Head 3 59 | ------ | ------ | ------ 60 | cell 1 | cell 2 | cell 3 61 | cell 4 | cell 5 | cell 6 62 | 63 | ## Multi-line tools: mo 64 | 65 | These all act on a selection containing multiple lines. 66 | 67 | Tool | Line before | Line after | Notes 68 | -----| ----------- | ---------- | ----- 69 | | `abc` | `> abc` | blockquotes 70 | | `abc` | `* abc` | bulleted list 71 | | `abc` | `1. abc` | numbered list 72 | mo | `abc` | `abc` | monospaced text or code block 73 | 74 | Text in blockquotes ( ) looks like: 75 | 76 | > here is 77 | > some 78 | > text 79 | 80 | Text in a bulleted list ( ) looks like: 81 | 82 | * here is 83 | * some 84 | * text 85 | 86 | Text in a numbered list ( ) looks like: 87 | 88 | 1. here is 89 | 2. some 90 | 3. text 91 | 92 | A multi-line code block ( mo ) looks like: 93 | ``` 94 | here is 95 | some 96 | text 97 | ``` 98 | 99 | ## See also 100 | 101 | * [Help pages](help) 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /data/catwiki/Editing.md: -------------------------------------------------------------------------------- 1 | # Editing pages in CatWiki 2 | 3 | *to do...* 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /data/catwiki/Installing CatWiki.md: -------------------------------------------------------------------------------- 1 | # Installing CatWiki 2 | 3 | Instructions to install CatWiki. 4 | 5 | ## Prerequisites 6 | 7 | You must have Python 2.7 and Git installed on your system. 8 | 9 | ## Download 10 | 11 | Download the software using `git`: 12 | 13 | $ git clone git@github.com:cabalamat/catwiki.git 14 | 15 | Go into the new directory: 16 | 17 | $ cd catwiki 18 | 19 | Set up a virtual environment: 20 | 21 | $ virtualenv venv 22 | $ . venv/bin/activate 23 | 24 | Use `pip` to download the dependencies: 25 | 26 | $ pip install -r requirements.txt 27 | 28 | ## Run the software 29 | 30 | Now you can either run the software using the Tornado web server, for production. Or for development you can use the Flask web server. 31 | 32 | ### Run with production web server 33 | 34 | $ cd app 35 | $ python server.py 36 | 37 | ### Run with development web server 38 | 39 | $ cd app 40 | $ python main.py 41 | 42 | ## Use the software 43 | 44 | Now point your web browser to . This will display [all the wikis](multiple wikis) 45 | in your installation. 46 | -------------------------------------------------------------------------------- /data/catwiki/Markdown.md: -------------------------------------------------------------------------------- 1 | # Markdown 2 | 3 | Markdown is the lightweight markup language that CatWiki uses. 4 | 5 | Markdown was chosen because: 6 | 7 | * the syntax is quite good 8 | * it is widely used 9 | * the Python Markdown package is extensible 10 | * I'm familiar with it, having used it before. 11 | 12 | ## See also 13 | 14 | * [[CatWiki enhancements to Markdown]] 15 | * [Markdown syntax](syntax) 16 | * [Wikipedia page on Markdown](https://en.wikipedia.org/wiki/Markdown) 17 | * [Python-Markdown on Pythonhosted](https://pythonhosted.org/Markdown/index.html) (also: [PyPI](https://pypi.python.org/pypi/Markdown), [GitHub](https://github.com/waylan/Python-Markdown)) 18 | -------------------------------------------------------------------------------- /data/catwiki/article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/data/catwiki/article.png -------------------------------------------------------------------------------- /data/catwiki/article_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/data/catwiki/article_editor.png -------------------------------------------------------------------------------- /data/catwiki/development/MeowCat Cat Icons.md: -------------------------------------------------------------------------------- 1 | # MeowCat cat icons 2 | 3 | The [Noun Project](http://thenounproject.com/) has some [cat icons](http://thenounproject.com/search/?q=cat), including [this](http://thenounproject.com/term/cat/52806/) and [this](http://thenounproject.com/term/cat/18061/). -------------------------------------------------------------------------------- /data/catwiki/development/Short-term to-do list.md: -------------------------------------------------------------------------------- 1 | # Short-term to-do list 2 | 3 | This is CatWiki's **Short-term to-do list**. 4 | 5 | [TOC] 6 | 7 | ## Allow articles to be re-named 8 | 9 | When an article is re-named it should behave like it does in Wikipedia; that is, links to the old name should still work. 10 | -------------------------------------------------------------------------------- /data/catwiki/development/home.md: -------------------------------------------------------------------------------- 1 | # Development documentation for SiteBox 2 | 3 | * [To-do list](todo) 4 | * [[long term development plan]] 5 | 6 | ## Data directories 7 | 8 | These are: 9 | 10 | * under `./data/` for SiteBox's internal wiki (the *SiteBox SiteBox*), where `./` means the top application directory. 11 | * under `~/siteboxdata/sites/` for all wikis that aren't themselves part of the SiteBox project. 12 | 13 | ## URLs 14 | 15 | * /{site}/w/{path} = view a wiki page or directory 16 | * /{site}/wikiedit/{path} = view a wiki page or directory 17 | * /_allSites = list all sites on this SiteBox installation 18 | * /{site}/info = information about a site 19 | 20 | ## Wiki conventions 21 | 22 | In any site or site subdirectory, `home` is the home page of that site/directory. (So the filename would be `home.md`). 23 | 24 | In any directory, `contents` (file `contents.md`) lists the pages (and subdirectories) of that directory in order. This is used when making a printed book (or ebook, etc). 25 | -------------------------------------------------------------------------------- /data/catwiki/development/long term development plan.md: -------------------------------------------------------------------------------- 1 | # Long term development plan 2 | 3 | *See also [SiteBox To-do List](todo) for a more short-term to-do list*. 4 | 5 | Get the wiki working as a standalone home wiki. 6 | 7 | Add users, so the system can be put on a public-facing website. A user can create new sites and can administer which other users can edit those sites. 8 | 9 | Add [[versioning]]. 10 | 11 | Add blogging/messaging (like the old meowcat). 12 | 13 | Add auto-synchronise so that a user can have a replica of a public site on their local computer. The two sites would auto-synchronise. If the site is one they have write-access to, then editing their local site should push changes to the public one. Communication should be encrypted. 14 | 15 | Add messaging from private site to private site. Communication should be encrypted. This would include messaging as well as wiki updates. 16 | -------------------------------------------------------------------------------- /data/catwiki/development/redirection.md: -------------------------------------------------------------------------------- 1 | # Redirection 2 | 3 | MediaWiki does redirects by having a page `foo` containing 4 | 5 | ``` 6 | #REDIRECT [ [bar] ] 7 | ``` 8 | 9 | This means that when a user follows a link `[[foo]]` they are redirected to `[[bar]]` and the system says they've been redirected. 10 | 11 | In SiteBox, `#` is a special character (at the start of a line it puts text in an `

` tag), so it may make sense to use another character, for example `!`: 12 | 13 | !REDIRECT [bar] 14 | 15 | In English text, "!" is rarely immediately followed by a alphabetic character, so this combination would be a good one for SiteBox special codes. -------------------------------------------------------------------------------- /data/catwiki/development/todo.md: -------------------------------------------------------------------------------- 1 | # SiteBox To-do List 2 | 3 | *This is my immediate to-do list. For a longer-term development schedule see [[long term development plan]]*. 4 | 5 | A list of things to be done for SiteBox. 6 | 7 | [TOC] 8 | 9 | ## Immediate 10 | 11 | *The immediate goal is to get SiteBox to a state where it can be used like [MarkWiki](https://github.com/mblayman/markwiki)*. 12 | 13 | Add a "Move page" or "Rename page" button, linked to a facility for [[redirection]] (like Mediawiki [has](http://www.mediawiki.org/wiki/Help:Redirects)). 14 | 15 | Improve formatting / CSS to make it look nice. E.g. formating of monospaced text, code, and blockquotes. Add language-definable code highlighting for source code. 16 | 17 | The name SiteBox can be confused with the [http://www.sitebox.com/](http://www.sitebox.com/) website. Change branding to MeowCat to avoid this and because I have the `meowc.at` domain. Perhaps use [this icon](http://www.flaticon.com/free-icon/kitty-front_23401) or something similar. See *[[MeowCat Cat Icons]]*. 18 | 19 | On the [[redirection]] page, text with two `[[` followed by two `]]` is incorrectly rendered; fix this. 20 | 21 | ### Change rendering of markdown to HTML 22 | 23 | Change Python-Markdown's TOC extension so that it takes parameters for what header-levels it puts into the TOC. (I want `h2`-`h5`; it uses `h1`-`h6`). 24 | 25 | Add to Markdown the facility to indent a paragraph (MediaWiki uses `:` for this). 26 | 27 | ### Done 28 | 29 | Add [Markdown extensions](Markdown/extensions) to SiteBox --DONE 30 | 31 | Allow the existence of multiple sites; the list of sites should be browsable. Allow the existence of multiple roots for sites, i.e. some could be under `sitebox/data/` and some under `~/mylocalwiki/`, the latter would not be private and not part of the SiteBox project. --DONE, but will revisit later, when there is a config system as part of the app. 32 | 33 | Make it work correctly with Unicode (e.g. óóóóó) characters in the Markdown source. --DONE 34 | 35 | Make the "Delete page" button work. --DONE 36 | 37 | Maybe indicate external links. Font Awesome's `fa-external-link` (this character: ) would work. --DONE 38 | 39 | Make literal URLs in the source be interpreted as links, e.g. source of `http://www.reddit.com/` should become [http://www.reddit.com/](http://www.reddit.com/) -- DONE: like this 40 | 41 | ## Later 42 | 43 | Package as an open source project (see [Open Sourcing a Python Project the Right Way](http://www.jeffknupp.com/blog/2013/08/16/open-sourcing-a-python-project-the-right-way/)). 44 | 45 | Add version control to pages on sitebox wikis (perhaps use Git?) 46 | 47 | Allow editing a section of a page, as well as a whole page. 48 | 49 | Make the table of contents look nicer: (i) only include sections from `##` downwards (ii) add numbering for sections, as on main part of article. 50 | 51 | Markdown doesn't have a syntax to indent a paragraph. Add one. Note that indentation is a separate concept from blockquoting. 52 | 53 | Do something about differently-capitalised versions of the same article e.g. `[[Python.]]` versus `[[python.]]`. 54 | 55 | ## Unresolved issues 56 | 57 | ATM you can have a page and a subdirectory with the same name. Is this potentially confusing/unintuitive? For example if you have a page `/foo/bar` then while folder `/foo/` exists, article `/foo` might well not exist. Where there is both an article and a folder under it, maybe they should be merged. Also there is the issue with `/foo/home` which might also serve the purpose of being a top-level article for foo. 58 | 59 | * One way to resolve this might be to deprecate pages such as `/foo/home` and just use `/foo` for the home page. If a page `/xyz` exists and there is also an `/xyz/` directory then the page might have a home icon () to the right of it in the location bar to indicate this. Also, at the end of the page might be a horizontal rule followed by a list of subpages and subfolders and a link to `/xyz/`. 60 | 61 | Remove pain points that arise when using SiteBox to document SiteBox (this will enable dogfooding). These are: 62 | 63 | * Whether the title of a page should be determined by its URL (as with MediaWiki) or separate (as currently with SiteBox) 64 | * Should page identifiers/titles have to begin with a capital? ATM we have some pages with the identifier "Home" and some with "home". 65 | 66 | -------------------------------------------------------------------------------- /data/catwiki/development/versioning.md: -------------------------------------------------------------------------------- 1 | # Versioning 2 | 3 | It would be very useful if the site could track old versions of pages, like Wikipedia can. 4 | 5 | One way to do this would be to use a MongoDB database, with a collection `pageVersion` with these fields: 6 | 7 | * `_id` = needed by MongoDB 8 | 9 | These form a composite primary key: 10 | 11 | * `site` = the name of the site it is part of 12 | * `wikiAddress` = this is the address within the wiki (a partial URL such as `development/versioning`) 13 | * `hash` = a hash of the contents 14 | * `timestamp` = timestamp of version in as a string in format `"yyyymmddhhmmss"`. Yes MongoDB has its own date format but it's a PITA 15 | 16 | Other fields: 17 | 18 | * `contents` = the contents of the page 19 | 20 | -------------------------------------------------------------------------------- /data/catwiki/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | *Answers to frequently-asked questions about CatWiki*. 4 | 5 | ## What is CatWiki? 6 | 7 | It is software to create simple wikis that can be used, for example, as a personal note system. 8 | 9 | ## Why is CatWiki? I.e. what was the reason it was created? 10 | 11 | CatWiki's creator used to use MediaWiki for the purpose but it takes some effort to set up (it uses a MySQL database) and you can't read the pages without the software working. 12 | 13 | CatWiki's pages are stored as text files so you can read them without using the CatWiki software. 14 | 15 | ## What Markup language does CatWiki use? 16 | 17 | CatWiki-[flavoured](CatWiki enhancements to Markdown) [[Markdown]]. 18 | -------------------------------------------------------------------------------- /data/catwiki/folder_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabalamat/catwiki/07411928f46cc2f1d9731d32da61132716b1fae0/data/catwiki/folder_view.png -------------------------------------------------------------------------------- /data/catwiki/help.md: -------------------------------------------------------------------------------- 1 | # Help 2 | 3 | This is the main help page for [CatWiki](home). 4 | 5 | * [CatWiki Home Page](home) 6 | * [Frequently Asked Questions](faq) 7 | * [Markdown](Markdown) 8 | * [[Editing]] including the [[Editing toolbar]] -------------------------------------------------------------------------------- /data/catwiki/home.md: -------------------------------------------------------------------------------- 1 | # CatWiki Home Page 2 | 3 | CatWiki is very simple wiki software that stores its articles as text files. It was originally called *SiteBox*. 4 | 5 | ## Features 6 | 7 | * Stores articles as text files, so they are easy to back up and manipulate. Even if the wiki software goes down, you can still get at your notes. 8 | * Uses an [enhanced](CatWiki enhancements to Markdown) version of the the [[Markdown]] markup language 9 | * Allows [[multiple wikis]] per installation, each one in its own directory. 10 | * Allows subdirectories. You can navigate through the directories using the web interface. If the directory includes image files, the web page shows thumbnails of them. 11 | * You can add [ Font Awesome icons](http://fontawesome.io/icons/) in your wiki articles. 12 | 13 | ## Technology Used 14 | 15 | CatWiki is written in Python and uses the Flask lightweight web framework. 16 | 17 | ## Layout of the CatWiki Wiki 18 | 19 | * general information about CatWiki, including [help](help) pages, is in the [root directory](./). 20 | * The sandbox is in the [sandbox directory](sandbox/). 21 | * Notes on development of Catwiki are in [development/](development/) . 22 | * Notes on the overall [MeowCat project](meowcat/home) are in [meowcat/](meowcat/). 23 | 24 | ## See also 25 | 26 | * [[Installing CatWiki]] 27 | * [Help pages](help) 28 | * [[Screenshots]] 29 | * [Frequently Asked Questions](faq) 30 | * CatWiki is part of the [MeowCat](meowcat/home) project. 31 | * [[Short-term to-do list]] 32 | * [[Credits]] 33 | -------------------------------------------------------------------------------- /data/catwiki/meowcat/Goals of MeowCat.md: -------------------------------------------------------------------------------- 1 | # Goals of MeowCat 2 | 3 | [TOC] 4 | 5 | ## Three original concepts 6 | 7 | The ideas around [MeowCat](home) stemmed from what were originally three separate ideas for projects in my mind: 8 | 9 | * **Concept 1 -- ???**: Anti-censorship software that would enable secure private communication and anonymous public communication (this concept I called **MeowCat**) 10 | 11 | * **Concept 2 -- ????**: A "website in a box" site (hence the name **SiteBox**) containing a blog, a wiki, and other tools that a group of people who want to communicate/collaborate on the internet might want. Think of it as [Wordpress](https://wordpress.com/) but with a built-in wiki. 12 | 13 | * **Concept 3** (no name): A platform for writing books and technical documentation, ideally collaboratively. Think of a web-based front-end to [Pandoc](http://johnmacfarlane.net/pandoc/) coupled with an intuitive user interface to [Git](http://git-scm.com/) and [Github](https://github.com/)-like functionality. [GitBook](https://www.gitbook.io/) is a project along these lines. *"Github for writers"* might be a good slogan for this part of the project. 14 | 15 | - - - - 16 | 17 | ## Putting them together 18 | 19 | The more I thought about these ideas, the more I realised there was considerable overlap between them. 20 | 21 | Concepts 1 and 2 both need a system where people can post messages, reply to others and have threaded conversations. Concepts 3 would also benefit from this as an aid to collaboration. 22 | 23 | Concepts 2 and 3 both include wikis. Concept 1 would benefit from wiki-like functionality. 24 | 25 | Put together Concept 1 and Concept 3, and you have people collaboratively writing books in secret, without having to meet face to face or knowing each others' identities. 26 | 27 | ## Goals in detail 28 | 29 | The three concepts I've listed above are somewhat vague in detail. To flesh them out, I've detailed various [use-cases](MeowCat Use-Cases). 30 | 31 | ## Implementation 32 | 33 | This is a big project that can't be done all at once. So it will be done in stages. 34 | 35 | ### Stage 1: dogfood 36 | 37 | The first stage is to get the project good enough to be self-hosting, i.e. so I can document the SiteBox project in itself. 38 | 39 | ### Open sourcing 40 | 41 | The project will be published on GitHub. I'll use the checklist in [open-sourcing a Python project the right way](http://www.jeffknupp.com/blog/2013/08/16/open-sourcing-a-python-project-the-right-way/). 42 | 43 | ### Deploying it to my meowc.at server 44 | 45 | Once the software is ready I will deploy it to my server at `meowc.at`. 46 | 47 | This will serve two purposes: 48 | 49 | 1. people will be able to use it to create their own websites/wikis 50 | 51 | 2. it will contain the documentation for SiteBox 52 | -------------------------------------------------------------------------------- /data/catwiki/meowcat/Ideas for features to add.md: -------------------------------------------------------------------------------- 1 | # Ideas for features to add 2 | 3 | **Discusses some features that could be added to the system**. 4 | 5 | ## Upvoting / downvoting 6 | 7 | Like on Reddit. But with a twist: when I upvote a post the system looks at all the other people who've upvoted the same post, and calculates their upvotes to other posts higher. 8 | 9 | This means that every user will have their own personalised upvote-scote for each post. 10 | 11 | Upvoting doesn't work very well with the decentralised nature of some aspects of the system, but it might be made to work when the system is a centralised website. 12 | 13 | ## Something like Instagram 14 | 15 | Where you can take a photo and quickly sent it to all your followers 16 | 17 | (Needs to be an app as well as a website; possibly have more than one app). 18 | 19 | ## RSS feeds 20 | 21 | The app should be both a consumer and producer of RSS feeds. -------------------------------------------------------------------------------- /data/catwiki/meowcat/MeowCat Use-Cases.md: -------------------------------------------------------------------------------- 1 | # MeowCat Use-Cases 2 | 3 | These use cases are a series of scenarios of how MeowCat might be used. 4 | 5 | ## Wiki scenarios 6 | 7 | ## Blogging scenarios 8 | 9 | ## MeowCat scenarios 10 | 11 | ## Book-creation scenarios 12 | -------------------------------------------------------------------------------- /data/catwiki/meowcat/home.md: -------------------------------------------------------------------------------- 1 | # The MeowCat project 2 | 3 | The **MeowCat project** is the larger project of which [CatWiki](../home) is a part. 4 | 5 | MeowCat is my project for a combined wiki/blog platform. 6 | 7 | ## Goals of MeowCat 8 | 9 | The overall goal of MeowCat is to be an all-encompassing platform where people can communicate and collaborate over the Internet. 10 | 11 | This overall goal is split into numerous use-cases, not all of which will be implemented at once. 12 | 13 | ## developing MeowCat 14 | 15 | 16 | ## See also 17 | 18 | * [[similar projects]] to MeowCat 19 | * [[similar ideas]] to MeowCat 20 | * [[Goals of MeowCat]] 21 | 22 | -------------------------------------------------------------------------------- /data/catwiki/meowcat/similar ideas.md: -------------------------------------------------------------------------------- 1 | # Similar ideas to MeowCat 2 | 3 | This page lists ideas that are similar to the MeowCat project. 4 | 5 | ## Page-Forkable Wikis 6 | 7 | At -------------------------------------------------------------------------------- /data/catwiki/meowcat/similar projects.md: -------------------------------------------------------------------------------- 1 | # Similar Projects 2 | 3 | Similar projects to MeowCat include: 4 | 5 | ## Encryption 6 | 7 | * [Tutanota](https://tutanota.com/) is an encrypted email program which works with Android and iOS. 8 | * [Mailpile](https://www.mailpile.is/) is an encrypted email program that has a web interface a bit like gmail. 9 | * [Charme aka Noisecrypt](https://github.com/mschultheiss/Noisecrypt) bills itself as "A decentralized social network with end-to-end encryption for messaging, private posts and private profile data." 10 | * [Zeronet](https://github.com/HelloZeroNet/ZeroNet) is a way of having anonymous, decentralised websites accessible over a P2P network 11 | 12 | ## Messaging software 13 | 14 | * [Telegram](https://telegram.org/) is messaging software for mobile phones. 15 | 16 | ## Collaborative software 17 | 18 | * [Gitbook](https://www.gitbook.com/) allows people to collaboratively create documents/wikis with Markdown and Git. 19 | 20 | ## Software to create websites 21 | 22 | * [NodeBB](https://github.com/NodeBB/NodeBB) -- forum software written in JavaScript / NodeJS. [Discussion on HN](https://news.ycombinator.com/item?id=7930586) 23 | 24 | ## Hosting websites 25 | 26 | * [Enterprise wiki](http://enterprisewiki.co) allows anyone to quickly create a wiki. [Discussion on HN](https://news.ycombinator.com/item?id=10457441), Oct 2015. 27 | * [Simple Site](http://www.simplesite.com/) allows you to create a website that they will then host. 28 | * [Wix](http://www.wix.com/) allows you to create a website that they will then host. 29 | 30 | ## Communities 31 | 32 | * [IndieWeb](https://indieweb.org/) is "a people-focused alternative to the 'corporate web'" and a community to build projects that achieve this. 33 | 34 | ## See also 35 | 36 | * [[Similar ideas to MeowCat]] 37 | -------------------------------------------------------------------------------- /data/catwiki/multiple wikis.md: -------------------------------------------------------------------------------- 1 | # Multiple Wikis 2 | 3 | CatWiki allows multiple wikis (aka sites) per installation, with each individual wiki being 4 | stored in its own directory (which can have subdirectories). 5 | 6 | The *catwiki* wiki exists by default and contains documentation and help pages for CatWiki 7 | itself. 8 | 9 | Other wikis are stored under `~/siteboxdata/sites/` so a wiki called **foo** would go in the 10 | directory `~/siteboxdata/sites/foo/`. 11 | 12 | To create a new wiki, just make a directory for it under `~/siteboxdata/sites/`. 13 | 14 | ## View all sites 15 | 16 | The URL lists all the wikis in a CatWiki installation. 17 | -------------------------------------------------------------------------------- /data/catwiki/sandbox.md: -------------------------------------------------------------------------------- 1 | # CatWiki's sandbox 2 | 3 | The **sandbox** is a safe area to try things out. It's stored in [the `/sandbox/` directory](sandbox/). 4 | 5 | [About the sandbox](sandbox/about) 6 | 7 | Levels of headings: 8 | 9 | # Cat icons 10 | 11 | * e600 12 | * e601 13 | 14 | ## Heading 2 15 | 16 | This is the main level of heading in a page. 17 | 18 | ### Heading 3 19 | 20 | This is used as the main subheading level. 21 | 22 | #### Heading 4 23 | 24 | Level 4 headings (and below) should typically be used rarely. 25 | 26 | ##### Heading 5 27 | 28 | Level 5 headings aren't very prominent. 29 | 30 | ## Small Cat Icon 31 | 32 | ## Big Cat Icon 33 | 34 | -------------------------------------------------------------------------------- /data/catwiki/sandbox/abc/bar2.md: -------------------------------------------------------------------------------- 1 | # Bar 2 2 | 3 | is bar! and two! -------------------------------------------------------------------------------- /data/catwiki/sandbox/abc/ddd/ggg.md: -------------------------------------------------------------------------------- 1 | # GGG 2 | 3 | Giggle. Giggle. Giggle. -------------------------------------------------------------------------------- /data/catwiki/sandbox/abc/def/ghi.md: -------------------------------------------------------------------------------- 1 | # GHI 2 | 3 | Good High Inside -------------------------------------------------------------------------------- /data/catwiki/sandbox/about.md: -------------------------------------------------------------------------------- 1 | # About the sandbox 2 | 3 | It is sandy. And boxy. 4 | 5 | Some other files in the sandbox: 6 | 7 | * [abc/def/ghi](abc/def/ghi) 8 | * [abc/ddd/ggg](abc/ddd/ggg) 9 | * [abc/foo](abc/foo) 10 | * [abc/bar](abc/bar) -------------------------------------------------------------------------------- /data/catwiki/screenshots.md: -------------------------------------------------------------------------------- 1 | # Screenshots 2 | 3 | Here are some screenshots of CatWiki in action. 4 | 5 | ## An article 6 | 7 | Here is an article in CatWiki: 8 | 9 | ![](article.png) 10 | 11 | ## The article editor 12 | 13 | Here is the same article in CatWiki's text editor: 14 | 15 | ![](article_editor.png) 16 | 17 | ## Folder view 18 | 19 | This shows all the articles, other files, and sub-folders in a folder. 20 | 21 | ![](folder_view.png) 22 | 23 | -------------------------------------------------------------------------------- /data/catwiki/syntax.md: -------------------------------------------------------------------------------- 1 | # Markdown syntax 2 | 3 | *This page describes the version of markdown syntax used by Sitebox. See also [[extensions]].* 4 | 5 | Below is a table of contents. It can be produced using the markup `[TOC]`. 6 | 7 | [TOC] 8 | 9 | ## Images 10 | 11 | Inline-style: 12 | 13 | ![alt text](url/goes/here.png "The Title") 14 | 15 | ## Source code 16 | 17 | Text with `tt` some computer `code` in it. 18 | 19 | Python source code: 20 | 21 | ```python 22 | def convertQuickLinks(s): 23 | """ Converts [[xxx]] -> [xxx](xxx) 24 | @param s [str] containing markdown source 25 | @return [str] 26 | """ 27 | QUICKLINK_RE = r"\[\[([A-Za-z0-9_ ]+)\]\]" 28 | REPLACE_WITH = r"[\1](\1)" 29 | r = re.sub(QUICKLINK_RE, REPLACE_WITH, s) 30 | return r 31 | ``` 32 | 33 | ## Tables 34 | 35 | Here is a table: 36 | 37 | First Header | Second Header 38 | ------------- | ------------- 39 | Content Cell | Content Cell 40 | Content Cell | Content Cell 41 | 42 | And here's another: 43 | 44 | | Function name | Description | 45 | | ------------- | ------------------------------ | 46 | | `help()` | Display the help window. | 47 | | `destroy()` | **Destroy your computer!** | 48 | 49 | ## Markup within a paragraph 50 | 51 | This includes *italic*, **bold**, and `monospaced` text. -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asn1crypto==0.24.0 2 | backports-abc==0.4 3 | backports.ssl-match-hostname==3.5.0.1 4 | certifi==2016.2.28 5 | cffi==1.11.5 6 | Click==7.0 7 | cryptography==2.3.1 8 | enum34==1.1.2 9 | Flask==0.12.3 10 | gitdb2==2.0.3 11 | GitPython==2.1.8 12 | idna==2.7 13 | ipaddress==1.0.16 14 | itsdangerous==0.24 15 | Jinja2==2.8 16 | Markdown==2.6.5 17 | MarkupSafe==0.23 18 | ndg-httpsclient==0.4.0 19 | pyasn1==0.1.9 20 | pycparser==2.14 21 | Pygments==2.1 22 | pyOpenSSL==18.0.0 23 | singledispatch==3.4.0.3 24 | six==1.10.0 25 | smmap2==2.0.3 26 | tornado==4.3 27 | Werkzeug==0.11.3 28 | -------------------------------------------------------------------------------- /runme: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # runme = run catwiki 4 | 5 | echo "*** run catwiki using Tornado ***" 6 | echo " you are in " `pwd` 7 | . venv/bin/activate 8 | cd app; python server.py 9 | 10 | 11 | #end 12 | -------------------------------------------------------------------------------- /startup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # startup = run programs at start-up 4 | # (put this in a directory in your PATH) 5 | 6 | cd ~/sproj/catwiki 7 | nohup ./runme 8 | 9 | 10 | #end 11 | --------------------------------------------------------------------------------