feat(SublimeText2.EditorPackages): cache packages
This commit is contained in:
2
EthanBrown.SublimeText2.EditorPackages/tools/PackageCache/Markdown Preview/.gitattributes
vendored
Normal file
2
EthanBrown.SublimeText2.EditorPackages/tools/PackageCache/Markdown Preview/.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
2
EthanBrown.SublimeText2.EditorPackages/tools/PackageCache/Markdown Preview/.gitignore
vendored
Normal file
2
EthanBrown.SublimeText2.EditorPackages/tools/PackageCache/Markdown Preview/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.pyc
|
||||
.DS_Store
|
@@ -0,0 +1,28 @@
|
||||
[
|
||||
{
|
||||
"caption": "Markdown Preview: preview in Browser",
|
||||
"command": "markdown_preview",
|
||||
"args": {
|
||||
"target": "browser"
|
||||
}
|
||||
},
|
||||
{
|
||||
"caption": "Markdown Preview: export HTML in Sublime Text",
|
||||
"command": "markdown_preview",
|
||||
"args": {
|
||||
"target": "sublime"
|
||||
}
|
||||
},
|
||||
{
|
||||
"caption": "Markdown Preview: copy to clipboard",
|
||||
"command": "markdown_preview",
|
||||
"args": {
|
||||
"target": "clipboard"
|
||||
}
|
||||
},
|
||||
{
|
||||
"caption": "Markdown Preview: open Markdown Cheat sheet",
|
||||
"command": "markdown_cheatsheet",
|
||||
"args": {}
|
||||
}
|
||||
]
|
@@ -0,0 +1,35 @@
|
||||
[
|
||||
{
|
||||
"caption": "Preferences",
|
||||
"mnemonic": "n",
|
||||
"id": "preferences",
|
||||
"children":
|
||||
[
|
||||
{
|
||||
"caption": "Package Settings",
|
||||
"mnemonic": "P",
|
||||
"id": "package-settings",
|
||||
"children":
|
||||
[
|
||||
{
|
||||
"caption": "Markdown Preview",
|
||||
"children":
|
||||
[
|
||||
{
|
||||
"command": "open_file",
|
||||
"args": {"file": "${packages}/Markdown Preview/MarkdownPreview.sublime-settings"},
|
||||
"caption": "Settings – Default"
|
||||
},
|
||||
{
|
||||
"command": "open_file",
|
||||
"args": {"file": "${packages}/User/MarkdownPreview.sublime-settings"},
|
||||
"caption": "Settings – User"
|
||||
},
|
||||
{ "caption": "-" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@@ -0,0 +1,217 @@
|
||||
# -*- encoding: UTF-8 -*-
|
||||
import sublime
|
||||
import sublime_plugin
|
||||
import desktop
|
||||
import tempfile
|
||||
import markdown2
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import json
|
||||
import urllib2
|
||||
|
||||
|
||||
settings = sublime.load_settings('MarkdownPreview.sublime-settings')
|
||||
|
||||
|
||||
def getTempMarkdownPreviewPath(view):
|
||||
''' return a permanent full path of the temp markdown preview file '''
|
||||
tmp_filename = '%s.html' % view.id()
|
||||
tmp_fullpath = os.path.join(tempfile.gettempdir(), tmp_filename)
|
||||
return tmp_fullpath
|
||||
|
||||
|
||||
class MarkdownPreviewListener(sublime_plugin.EventListener):
|
||||
''' auto update the output html if markdown file has already been converted once '''
|
||||
|
||||
def on_post_save(self, view):
|
||||
if view.file_name().endswith(tuple(settings.get('markdown_filetypes', (".md", ".markdown", ".mdown")))):
|
||||
temp_file = getTempMarkdownPreviewPath(view)
|
||||
if os.path.isfile(temp_file):
|
||||
# reexec markdown conversion
|
||||
view.run_command('markdown_preview', {'target': 'disk'})
|
||||
sublime.status_message('Markdown preview file updated')
|
||||
|
||||
|
||||
class MarkdownCheatsheetCommand(sublime_plugin.TextCommand):
|
||||
''' open our markdown cheat sheet in ST2 '''
|
||||
def run(self, edit):
|
||||
cheatsheet = os.path.join(sublime.packages_path(), 'Markdown Preview', 'sample.md')
|
||||
self.view.window().open_file(cheatsheet)
|
||||
sublime.status_message('Markdown cheat sheet opened')
|
||||
|
||||
|
||||
class MarkdownPreviewCommand(sublime_plugin.TextCommand):
|
||||
''' preview file contents with python-markdown and your web browser '''
|
||||
|
||||
def getCSS(self):
|
||||
''' return the correct CSS file based on parser and settings '''
|
||||
config_parser = settings.get('parser')
|
||||
config_css = settings.get('css')
|
||||
|
||||
styles = ''
|
||||
if config_css and config_css != 'default':
|
||||
styles += u"<link href='%s' rel='stylesheet' type='text/css'>" % config_css
|
||||
else:
|
||||
css_filename = 'markdown.css'
|
||||
if config_parser and config_parser == 'github':
|
||||
css_filename = 'github.css'
|
||||
# path via package manager
|
||||
css_path = os.path.join(sublime.packages_path(), 'Markdown Preview', css_filename)
|
||||
if not os.path.isfile(css_path):
|
||||
# path via git repo
|
||||
css_path = os.path.join(sublime.packages_path(), 'sublimetext-markdown-preview', css_filename)
|
||||
if not os.path.isfile(css_path):
|
||||
sublime.error_message('markdown.css file not found!')
|
||||
raise Exception("markdown.css file not found!")
|
||||
styles += u"<style>%s</style>" % open(css_path, 'r').read().decode('utf-8')
|
||||
|
||||
if settings.get('allow_css_overrides'):
|
||||
filename = self.view.file_name()
|
||||
filetypes = settings.get('markdown_filetypes')
|
||||
|
||||
if filename and filetypes:
|
||||
for filetype in filetypes:
|
||||
if filename.endswith(filetype):
|
||||
css_filename = filename.rpartition(filetype)[0] + '.css'
|
||||
if (os.path.isfile(css_filename)):
|
||||
styles += u"<style>%s</style>" % open(css_filename, 'r').read().decode('utf-8')
|
||||
|
||||
return styles
|
||||
|
||||
def get_contents(self, region):
|
||||
''' Get contents or selection from view and optionally strip the YAML front matter '''
|
||||
contents = self.view.substr(region)
|
||||
# use selection if any
|
||||
selection = self.view.substr(self.view.sel()[0])
|
||||
if selection.strip() != '':
|
||||
contents = selection
|
||||
if settings.get('strip_yaml_front_matter') and contents.startswith('---'):
|
||||
title = ''
|
||||
title_match = re.search('(?:title:)(.+)', contents, flags=re.IGNORECASE)
|
||||
if title_match:
|
||||
stripped_title = title_match.group(1).strip()
|
||||
title = '%s\n%s\n\n' % (stripped_title, '=' * len(stripped_title))
|
||||
contents_without_front_matter = re.sub(r'(?s)^---.*---\n', '', contents)
|
||||
contents = '%s%s' % (title, contents_without_front_matter)
|
||||
return contents
|
||||
|
||||
def postprocessor(self, html):
|
||||
''' fix relative paths in images, scripts, and links for the internal parser '''
|
||||
def tag_fix(match):
|
||||
tag, src = match.groups()
|
||||
filename = self.view.file_name()
|
||||
if filename:
|
||||
if not src.startswith(('file://', 'https://', 'http://', '/', '#')):
|
||||
abs_path = u'file://%s/%s' % (os.path.dirname(filename), src)
|
||||
tag = tag.replace(src, abs_path)
|
||||
return tag
|
||||
RE_SOURCES = re.compile("""(?P<tag><(?:img|script|a)[^>]+(?:src|href)=["'](?P<src>[^"']+)[^>]*>)""")
|
||||
html = RE_SOURCES.sub(tag_fix, html)
|
||||
return html
|
||||
|
||||
def convert_markdown(self, markdown):
|
||||
''' convert input markdown to HTML, with github or builtin parser '''
|
||||
config_parser = settings.get('parser')
|
||||
github_oauth_token = settings.get('github_oauth_token')
|
||||
|
||||
markdown_html = u'cannot convert markdown'
|
||||
if config_parser and config_parser == 'github':
|
||||
# use the github API
|
||||
sublime.status_message('converting markdown with github API...')
|
||||
try:
|
||||
github_mode = settings.get('github_mode', 'gfm')
|
||||
data = {"text": markdown, "mode": github_mode}
|
||||
json_data = json.dumps(data)
|
||||
url = "https://api.github.com/markdown"
|
||||
sublime.status_message(url)
|
||||
request = urllib2.Request(url, json_data, {'Content-Type': 'application/json'})
|
||||
if github_oauth_token:
|
||||
request.add_header('Authorization', "token %s" % github_oauth_token)
|
||||
markdown_html = urllib2.urlopen(request).read().decode('utf-8')
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 401:
|
||||
sublime.error_message('github API auth failed. Please check your OAuth token.')
|
||||
else:
|
||||
sublime.error_message('github API responded in an unfashion way :/')
|
||||
except urllib2.URLError:
|
||||
sublime.error_message('cannot use github API to convert markdown. SSL is not included in your Python installation')
|
||||
except:
|
||||
sublime.error_message('cannot use github API to convert markdown. Please check your settings.')
|
||||
else:
|
||||
sublime.status_message('converted markdown with github API successfully')
|
||||
else:
|
||||
# convert the markdown
|
||||
markdown_html = markdown2.markdown(markdown, extras=['footnotes', 'toc', 'fenced-code-blocks', 'cuddled-lists'])
|
||||
toc_html = markdown_html.toc_html
|
||||
if toc_html:
|
||||
toc_markers = ['[toc]', '[TOC]', '<!--TOC-->']
|
||||
for marker in toc_markers:
|
||||
markdown_html = markdown_html.replace(marker, toc_html)
|
||||
|
||||
# postprocess the html from internal parser
|
||||
markdown_html = self.postprocessor(markdown_html)
|
||||
|
||||
return markdown_html
|
||||
|
||||
def run(self, edit, target='browser'):
|
||||
region = sublime.Region(0, self.view.size())
|
||||
encoding = self.view.encoding()
|
||||
if encoding == 'Undefined':
|
||||
encoding = 'utf-8'
|
||||
elif encoding == 'Western (Windows 1252)':
|
||||
encoding = 'windows-1252'
|
||||
elif encoding == 'UTF-8 with BOM':
|
||||
encoding = 'utf-8'
|
||||
|
||||
contents = self.get_contents(region)
|
||||
|
||||
markdown_html = self.convert_markdown(contents)
|
||||
|
||||
full_html = u'<!DOCTYPE html>'
|
||||
full_html += '<html><head><meta charset="%s">' % encoding
|
||||
full_html += self.getCSS()
|
||||
full_html += '</head><body>'
|
||||
full_html += markdown_html
|
||||
full_html += '</body>'
|
||||
full_html += '</html>'
|
||||
|
||||
if target in ['disk', 'browser']:
|
||||
# check if LiveReload ST2 extension installed and add its script to the resulting HTML
|
||||
livereload_installed = ('LiveReload' in os.listdir(sublime.packages_path()))
|
||||
if livereload_installed:
|
||||
full_html += '<script>document.write(\'<script src="http://\' + (location.host || \'localhost\').split(\':\')[0] + \':35729/livereload.js?snipver=1"></\' + \'script>\')</script>'
|
||||
# update output html file
|
||||
tmp_fullpath = getTempMarkdownPreviewPath(self.view)
|
||||
tmp_html = open(tmp_fullpath, 'w')
|
||||
tmp_html.write(full_html.encode(encoding))
|
||||
tmp_html.close()
|
||||
# now opens in browser if needed
|
||||
if target == 'browser':
|
||||
config_browser = settings.get('browser')
|
||||
if config_browser and config_browser != 'default':
|
||||
cmd = '"%s" %s' % (config_browser, tmp_fullpath)
|
||||
if sys.platform == 'darwin':
|
||||
cmd = "open -a %s" % cmd
|
||||
elif sys.platform == 'linux2':
|
||||
cmd += ' &'
|
||||
result = os.system(cmd)
|
||||
if result != 0:
|
||||
sublime.error_message('cannot execute "%s" Please check your Markdown Preview settings' % config_browser)
|
||||
else:
|
||||
sublime.status_message('Markdown preview launched in %s' % config_browser)
|
||||
else:
|
||||
desktop.open(tmp_fullpath)
|
||||
sublime.status_message('Markdown preview launched in default html viewer')
|
||||
elif target == 'sublime':
|
||||
# create a new buffer and paste the output HTML
|
||||
new_view = self.view.window().new_file()
|
||||
new_view.set_scratch(True)
|
||||
new_edit = new_view.begin_edit()
|
||||
new_view.insert(new_edit, 0, markdown_html)
|
||||
new_view.end_edit(new_edit)
|
||||
sublime.status_message('Markdown preview launched in sublime')
|
||||
elif target == 'clipboard':
|
||||
# clipboard copy the full HTML
|
||||
sublime.set_clipboard(full_html)
|
||||
sublime.status_message('Markdown export copied to clipboard')
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Markdown Preview default settings
|
||||
*/
|
||||
{
|
||||
/*
|
||||
Sets the default opener for html files
|
||||
|
||||
default - Use the system default HTML viewer
|
||||
other - Set a full path to any executable. ex: /Applications/Google Chrome Canary.app or /Applications/Firefox.app
|
||||
*/
|
||||
"browser": "default",
|
||||
|
||||
/*
|
||||
Sets the default parser for converting markdown to html.
|
||||
Warning for github API : if you have a ST2 linux build, Python is not built with SSL o it may not work
|
||||
|
||||
default - Use the builtin python-markdown2 parser
|
||||
github - User github API to convert markdown, so you can use GitHub flavored Markdown, see http://github.github.com/github-flavored-markdown/
|
||||
*/
|
||||
"parser": "default",
|
||||
|
||||
/*
|
||||
Default mode for the github Markdon parser : markdown (documents) or gfm (comments)
|
||||
see http://developer.github.com/v3/markdown/#render-an-arbitrary-markdown-document
|
||||
*/
|
||||
"github_mode": "markdown",
|
||||
|
||||
/*
|
||||
Uses an OAuth token to when parsing markdown with GitHub API. To create one for Markdown Preview, see https://help.github.com/articles/creating-an-oauth-token-for-command-line-use.
|
||||
*/
|
||||
// "github_oauth_token": "secret"
|
||||
|
||||
/*
|
||||
Sets the default css file to embed in the HTML
|
||||
|
||||
default - Use the builtin CSS or github CSS, depending on parser config (markdown.css or github.css)
|
||||
other - Set an absolute path or url to any css file
|
||||
*/
|
||||
"css": "default",
|
||||
|
||||
/*
|
||||
Allow CSS overrides
|
||||
|
||||
true - Any file with matching a .markdown_filetype extension with .css will be loaded as an override
|
||||
false - Matching files ignored
|
||||
*/
|
||||
"allow_css_overrides": true,
|
||||
|
||||
/*
|
||||
Sets the supported filetypes for auto-reload on save
|
||||
*/
|
||||
"markdown_filetypes": [".md", ".markdown", ".mdown"],
|
||||
|
||||
/*
|
||||
Strips the YAML front matter header and converts title to a heading
|
||||
*/
|
||||
"strip_yaml_front_matter": false
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,52 @@
|
||||
Sublime Text 2 MarkDown preview
|
||||
===============================
|
||||
|
||||
A simple ST2 plugin to help you preview your markdown files quickly in you web browser.
|
||||
|
||||
You can use builtin [python-markdown2][0] parser (default) or use the [github markdown API][5] for the conversion (edit your settings to select it).
|
||||
|
||||
**NOTE:** If you choose the GitHub API for conversion (set parser: github in your settings), your code will be sent through https to github for live conversion. You'll have [Github flavored markdown][6], syntax highlighting and EMOJI support for free :heart: :octocat: :gift:. If you make more than 60 calls a day, be sure to set your GitHub API key in the settings :)
|
||||
|
||||
**LINUX users:** If you want to use GitHub API for conversion, you'll need to have a custom Python install that includes python-ssl as its not built in the Sublime Text 2 Linux package. see [@dusteye comment][8]. If you use a custom window manager, also be sure to set a `BROWSER` environnement variable. see [@PPvG comments][9]
|
||||
|
||||
## Features :
|
||||
|
||||
- Markdown conversion via builtin Markdown Parser ([python-markdown2][0]) or via Github API : just choose in your settings.
|
||||
- Browser preview auto reload on save if you have the [ST2 LiveReload plugin][7] installed.
|
||||
- Builtin parser : Support TOC, footnotes markdown extensions
|
||||
- CSS overriding if you need
|
||||
- YAML support thanks to @tommi
|
||||
- Clipboard selection and copy to clipboard thanks to @hexatrope
|
||||
|
||||
## Installation :
|
||||
|
||||
- you should use [sublime package manager][3]
|
||||
- use `cmd+shift+P` then `Package Control: Install Package`
|
||||
- look for `Markdown Preview` and install it.
|
||||
|
||||
## Usage :
|
||||
|
||||
- optionnaly select some of your markdown for conversion
|
||||
- use `cmd+shift+P` then `Markdown Preview` to launch a preview
|
||||
- or bind some key in your user key binding, using a line like this one:
|
||||
`{ "keys": ["alt+m"], "command": "markdown_preview", "args": {"target": "browser"} },`
|
||||
- once converted a first time, the output HTML will be updated on each file save (with LiveReload plugin)
|
||||
|
||||
## Uses :
|
||||
|
||||
- [python-markdown2][0] for markdown parsing **OR** the GitHub markdown API.
|
||||
|
||||
|
||||
## Licence :
|
||||
|
||||
The code is available at github [https://github.com/revolunet/sublimetext-markdown-preview][2] under MIT licence : [http://revolunet.mit-license.org][4]
|
||||
|
||||
[0]: https://github.com/trentm/python-markdown2
|
||||
[2]: https://github.com/revolunet/sublimetext-markdown-preview
|
||||
[3]: http://wbond.net/sublime_packages/package_control
|
||||
[4]: http://revolunet.mit-license.org
|
||||
[5]: http://developer.github.com/v3/markdown
|
||||
[6]: http://github.github.com/github-flavored-markdown/
|
||||
[7]: https://github.com/dz0ny/LiveReload-sublimetext2
|
||||
[8]: https://github.com/revolunet/sublimetext-markdown-preview/issues/27#issuecomment-11772098
|
||||
[9]: https://github.com/revolunet/sublimetext-markdown-preview/issues/78#issuecomment-15644727
|
@@ -0,0 +1,290 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Simple desktop integration for Python. This module provides desktop environment
|
||||
detection and resource opening support for a selection of common and
|
||||
standardised desktop environments.
|
||||
|
||||
Copyright (C) 2005, 2006, 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the Free
|
||||
Software Foundation; either version 3 of the License, or (at your option) any
|
||||
later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
--------
|
||||
|
||||
Desktop Detection
|
||||
-----------------
|
||||
|
||||
To detect a specific desktop environment, use the get_desktop function.
|
||||
To detect whether the desktop environment is standardised (according to the
|
||||
proposed DESKTOP_LAUNCH standard), use the is_standard function.
|
||||
|
||||
Opening URLs
|
||||
------------
|
||||
|
||||
To open a URL in the current desktop environment, relying on the automatic
|
||||
detection of that environment, use the desktop.open function as follows:
|
||||
|
||||
desktop.open("http://www.python.org")
|
||||
|
||||
To override the detected desktop, specify the desktop parameter to the open
|
||||
function as follows:
|
||||
|
||||
desktop.open("http://www.python.org", "KDE") # Insists on KDE
|
||||
desktop.open("http://www.python.org", "GNOME") # Insists on GNOME
|
||||
desktop.open("http://www.python.org", "MATE") # Insists on MATE
|
||||
|
||||
Without overriding using the desktop parameter, the open function will attempt
|
||||
to use the "standard" desktop opening mechanism which is controlled by the
|
||||
DESKTOP_LAUNCH environment variable as described below.
|
||||
|
||||
The DESKTOP_LAUNCH Environment Variable
|
||||
---------------------------------------
|
||||
|
||||
The DESKTOP_LAUNCH environment variable must be shell-quoted where appropriate,
|
||||
as shown in some of the following examples:
|
||||
|
||||
DESKTOP_LAUNCH="kdialog --msgbox" Should present any opened URLs in
|
||||
their entirety in a KDE message box.
|
||||
(Command "kdialog" plus parameter.)
|
||||
DESKTOP_LAUNCH="my\ opener" Should run the "my opener" program to
|
||||
open URLs.
|
||||
(Command "my opener", no parameters.)
|
||||
DESKTOP_LAUNCH="my\ opener --url" Should run the "my opener" program to
|
||||
open URLs.
|
||||
(Command "my opener" plus parameter.)
|
||||
|
||||
Details of the DESKTOP_LAUNCH environment variable convention can be found here:
|
||||
http://lists.freedesktop.org/archives/xdg/2004-August/004489.html
|
||||
|
||||
Other Modules
|
||||
-------------
|
||||
|
||||
The desktop.dialog module provides support for opening dialogue boxes.
|
||||
The desktop.windows module permits the inspection of desktop windows.
|
||||
"""
|
||||
|
||||
__version__ = "0.4"
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Provide suitable process creation functions.
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
def _run(cmd, shell, wait):
|
||||
opener = subprocess.Popen(cmd, shell=shell)
|
||||
if wait: opener.wait()
|
||||
return opener.pid
|
||||
|
||||
def _readfrom(cmd, shell):
|
||||
opener = subprocess.Popen(cmd, shell=shell, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
opener.stdin.close()
|
||||
return opener.stdout.read()
|
||||
|
||||
def _status(cmd, shell):
|
||||
opener = subprocess.Popen(cmd, shell=shell)
|
||||
opener.wait()
|
||||
return opener.returncode == 0
|
||||
|
||||
except ImportError:
|
||||
import popen2
|
||||
def _run(cmd, shell, wait):
|
||||
opener = popen2.Popen3(cmd)
|
||||
if wait: opener.wait()
|
||||
return opener.pid
|
||||
|
||||
def _readfrom(cmd, shell):
|
||||
opener = popen2.Popen3(cmd)
|
||||
opener.tochild.close()
|
||||
opener.childerr.close()
|
||||
return opener.fromchild.read()
|
||||
|
||||
def _status(cmd, shell):
|
||||
opener = popen2.Popen3(cmd)
|
||||
opener.wait()
|
||||
return opener.poll() == 0
|
||||
|
||||
import commands
|
||||
|
||||
# Private functions.
|
||||
|
||||
def _get_x11_vars():
|
||||
|
||||
"Return suitable environment definitions for X11."
|
||||
|
||||
if not os.environ.get("DISPLAY", "").strip():
|
||||
return "DISPLAY=:0.0 "
|
||||
else:
|
||||
return ""
|
||||
|
||||
def _is_xfce():
|
||||
|
||||
"Return whether XFCE is in use."
|
||||
|
||||
# XFCE detection involves testing the output of a program.
|
||||
|
||||
try:
|
||||
return _readfrom(_get_x11_vars() + "xprop -root _DT_SAVE_MODE", shell=1).strip().endswith(' = "xfce4"')
|
||||
except OSError:
|
||||
return 0
|
||||
|
||||
def _is_x11():
|
||||
|
||||
"Return whether the X Window System is in use."
|
||||
|
||||
return os.environ.has_key("DISPLAY")
|
||||
|
||||
# Introspection functions.
|
||||
|
||||
def get_desktop():
|
||||
|
||||
"""
|
||||
Detect the current desktop environment, returning the name of the
|
||||
environment. If no environment could be detected, None is returned.
|
||||
"""
|
||||
|
||||
if os.environ.has_key("KDE_FULL_SESSION") or \
|
||||
os.environ.has_key("KDE_MULTIHEAD"):
|
||||
return "KDE"
|
||||
elif os.environ.has_key("GNOME_DESKTOP_SESSION_ID") or \
|
||||
os.environ.has_key("GNOME_KEYRING_SOCKET"):
|
||||
return "GNOME"
|
||||
elif os.environ.has_key("MATE_DESKTOP_SESSION_ID") or \
|
||||
os.environ.has_key("MATE_KEYRING_SOCKET"):
|
||||
return "MATE"
|
||||
elif sys.platform == "darwin":
|
||||
return "Mac OS X"
|
||||
elif hasattr(os, "startfile"):
|
||||
return "Windows"
|
||||
elif _is_xfce():
|
||||
return "XFCE"
|
||||
|
||||
# KDE, GNOME, MATE and XFCE run on X11, so we have to test for X11 last.
|
||||
|
||||
if _is_x11():
|
||||
return "X11"
|
||||
else:
|
||||
return None
|
||||
|
||||
def use_desktop(desktop):
|
||||
|
||||
"""
|
||||
Decide which desktop should be used, based on the detected desktop and a
|
||||
supplied 'desktop' argument (which may be None). Return an identifier
|
||||
indicating the desktop type as being either "standard" or one of the results
|
||||
from the 'get_desktop' function.
|
||||
"""
|
||||
|
||||
# Attempt to detect a desktop environment.
|
||||
|
||||
detected = get_desktop()
|
||||
|
||||
# Start with desktops whose existence can be easily tested.
|
||||
|
||||
if (desktop is None or desktop == "standard") and is_standard():
|
||||
return "standard"
|
||||
elif (desktop is None or desktop == "Windows") and detected == "Windows":
|
||||
return "Windows"
|
||||
|
||||
# Test for desktops where the overriding is not verified.
|
||||
|
||||
elif (desktop or detected) == "KDE":
|
||||
return "KDE"
|
||||
elif (desktop or detected) == "GNOME":
|
||||
return "GNOME"
|
||||
elif (desktop or detected) == "MATE":
|
||||
return "MATE"
|
||||
elif (desktop or detected) == "XFCE":
|
||||
return "XFCE"
|
||||
elif (desktop or detected) == "Mac OS X":
|
||||
return "Mac OS X"
|
||||
elif (desktop or detected) == "X11":
|
||||
return "X11"
|
||||
else:
|
||||
return None
|
||||
|
||||
def is_standard():
|
||||
|
||||
"""
|
||||
Return whether the current desktop supports standardised application
|
||||
launching.
|
||||
"""
|
||||
|
||||
return os.environ.has_key("DESKTOP_LAUNCH")
|
||||
|
||||
# Activity functions.
|
||||
|
||||
def open(url, desktop=None, wait=0):
|
||||
|
||||
"""
|
||||
Open the 'url' in the current desktop's preferred file browser. If the
|
||||
optional 'desktop' parameter is specified then attempt to use that
|
||||
particular desktop environment's mechanisms to open the 'url' instead of
|
||||
guessing or detecting which environment is being used.
|
||||
|
||||
Suggested values for 'desktop' are "standard", "KDE", "GNOME", "GNOME",
|
||||
"XFCE", "Mac OS X", "Windows" where "standard" employs a DESKTOP_LAUNCH
|
||||
environment variable to open the specified 'url'. DESKTOP_LAUNCH should
|
||||
be a command, possibly followed by arguments, and must have any special
|
||||
characters shell-escaped.
|
||||
|
||||
The process identifier of the "opener" (ie. viewer, editor, browser or
|
||||
program) associated with the 'url' is returned by this function. If the
|
||||
process identifier cannot be determined, None is returned.
|
||||
|
||||
An optional 'wait' parameter is also available for advanced usage and, if
|
||||
'wait' is set to a true value, this function will wait for the launching
|
||||
mechanism to complete before returning (as opposed to immediately returning
|
||||
as is the default behaviour).
|
||||
"""
|
||||
|
||||
# Decide on the desktop environment in use.
|
||||
|
||||
desktop_in_use = use_desktop(desktop)
|
||||
|
||||
if desktop_in_use == "standard":
|
||||
arg = "".join([os.environ["DESKTOP_LAUNCH"], commands.mkarg(url)])
|
||||
return _run(arg, 1, wait)
|
||||
|
||||
elif desktop_in_use == "Windows":
|
||||
# NOTE: This returns None in current implementations.
|
||||
return os.startfile(url)
|
||||
|
||||
elif desktop_in_use == "KDE":
|
||||
cmd = ["xdg-open", url]
|
||||
|
||||
elif desktop_in_use == "GNOME":
|
||||
cmd = ["xdg-open", url]
|
||||
|
||||
elif desktop_in_use == "MATE":
|
||||
cmd = ["xdg-open", url]
|
||||
|
||||
elif desktop_in_use == "XFCE":
|
||||
cmd = ["xdg-open", url]
|
||||
|
||||
elif desktop_in_use == "Mac OS X":
|
||||
cmd = ["open", url]
|
||||
|
||||
elif desktop_in_use == "X11" and os.environ.has_key("BROWSER"):
|
||||
cmd = [os.environ["BROWSER"], url]
|
||||
|
||||
# Finish with an error where no suitable desktop was identified.
|
||||
|
||||
else:
|
||||
raise OSError, "Desktop '%s' not supported (neither DESKTOP_LAUNCH nor os.startfile could be used)" % desktop_in_use
|
||||
|
||||
return _run(cmd, 0, wait)
|
||||
|
||||
# vim: tabstop=4 expandtab shiftwidth=4
|
@@ -0,0 +1,551 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Simple desktop dialogue box support for Python.
|
||||
|
||||
Copyright (C) 2007, 2009 Paul Boddie <paul@boddie.org.uk>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the Free
|
||||
Software Foundation; either version 3 of the License, or (at your option) any
|
||||
later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
--------
|
||||
|
||||
Opening Dialogue Boxes (Dialogs)
|
||||
--------------------------------
|
||||
|
||||
To open a dialogue box (dialog) in the current desktop environment, relying on
|
||||
the automatic detection of that environment, use the appropriate dialogue box
|
||||
class:
|
||||
|
||||
question = desktop.dialog.Question("Are you sure?")
|
||||
result = question.open()
|
||||
|
||||
To override the detected desktop, specify the desktop parameter to the open
|
||||
function as follows:
|
||||
|
||||
question.open("KDE") # Insists on KDE
|
||||
question.open("GNOME") # Insists on GNOME
|
||||
question.open("MATE") # Insists on MATE
|
||||
|
||||
The dialogue box options are documented in each class's docstring.
|
||||
|
||||
Available dialogue box classes are listed in the desktop.dialog.available
|
||||
attribute.
|
||||
|
||||
Supported desktop environments are listed in the desktop.dialog.supported
|
||||
attribute.
|
||||
"""
|
||||
|
||||
from desktop import use_desktop, _run, _readfrom, _status
|
||||
|
||||
class _wrapper:
|
||||
def __init__(self, handler):
|
||||
self.handler = handler
|
||||
|
||||
class _readvalue(_wrapper):
|
||||
def __call__(self, cmd, shell):
|
||||
return self.handler(cmd, shell).strip()
|
||||
|
||||
class _readinput(_wrapper):
|
||||
def __call__(self, cmd, shell):
|
||||
return self.handler(cmd, shell)[:-1]
|
||||
|
||||
class _readvalues_kdialog(_wrapper):
|
||||
def __call__(self, cmd, shell):
|
||||
result = self.handler(cmd, shell).strip().strip('"')
|
||||
if result:
|
||||
return result.split('" "')
|
||||
else:
|
||||
return []
|
||||
|
||||
class _readvalues_zenity(_wrapper):
|
||||
def __call__(self, cmd, shell):
|
||||
result = self.handler(cmd, shell).strip()
|
||||
if result:
|
||||
return result.split("|")
|
||||
else:
|
||||
return []
|
||||
|
||||
class _readvalues_Xdialog(_wrapper):
|
||||
def __call__(self, cmd, shell):
|
||||
result = self.handler(cmd, shell).strip()
|
||||
if result:
|
||||
return result.split("/")
|
||||
else:
|
||||
return []
|
||||
|
||||
# Dialogue parameter classes.
|
||||
|
||||
class String:
|
||||
|
||||
"A generic parameter."
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def convert(self, value, program):
|
||||
return [value or ""]
|
||||
|
||||
class Strings(String):
|
||||
|
||||
"Multiple string parameters."
|
||||
|
||||
def convert(self, value, program):
|
||||
return value or []
|
||||
|
||||
class StringPairs(String):
|
||||
|
||||
"Multiple string parameters duplicated to make identifiers."
|
||||
|
||||
def convert(self, value, program):
|
||||
l = []
|
||||
for v in value:
|
||||
l.append(v)
|
||||
l.append(v)
|
||||
return l
|
||||
|
||||
class StringKeyword:
|
||||
|
||||
"A keyword parameter."
|
||||
|
||||
def __init__(self, keyword, name):
|
||||
self.keyword = keyword
|
||||
self.name = name
|
||||
|
||||
def convert(self, value, program):
|
||||
return [self.keyword + "=" + (value or "")]
|
||||
|
||||
class StringKeywords:
|
||||
|
||||
"Multiple keyword parameters."
|
||||
|
||||
def __init__(self, keyword, name):
|
||||
self.keyword = keyword
|
||||
self.name = name
|
||||
|
||||
def convert(self, value, program):
|
||||
l = []
|
||||
for v in value or []:
|
||||
l.append(self.keyword + "=" + v)
|
||||
return l
|
||||
|
||||
class Integer(String):
|
||||
|
||||
"An integer parameter."
|
||||
|
||||
defaults = {
|
||||
"width" : 40,
|
||||
"height" : 15,
|
||||
"list_height" : 10
|
||||
}
|
||||
scale = 8
|
||||
|
||||
def __init__(self, name, pixels=0):
|
||||
String.__init__(self, name)
|
||||
if pixels:
|
||||
self.factor = self.scale
|
||||
else:
|
||||
self.factor = 1
|
||||
|
||||
def convert(self, value, program):
|
||||
if value is None:
|
||||
value = self.defaults[self.name]
|
||||
return [str(int(value) * self.factor)]
|
||||
|
||||
class IntegerKeyword(Integer):
|
||||
|
||||
"An integer keyword parameter."
|
||||
|
||||
def __init__(self, keyword, name, pixels=0):
|
||||
Integer.__init__(self, name, pixels)
|
||||
self.keyword = keyword
|
||||
|
||||
def convert(self, value, program):
|
||||
if value is None:
|
||||
value = self.defaults[self.name]
|
||||
return [self.keyword + "=" + str(int(value) * self.factor)]
|
||||
|
||||
class Boolean(String):
|
||||
|
||||
"A boolean parameter."
|
||||
|
||||
values = {
|
||||
"kdialog" : ["off", "on"],
|
||||
"zenity" : ["FALSE", "TRUE"],
|
||||
"Xdialog" : ["off", "on"]
|
||||
}
|
||||
|
||||
def convert(self, value, program):
|
||||
values = self.values[program]
|
||||
if value:
|
||||
return [values[1]]
|
||||
else:
|
||||
return [values[0]]
|
||||
|
||||
class MenuItemList(String):
|
||||
|
||||
"A menu item list parameter."
|
||||
|
||||
def convert(self, value, program):
|
||||
l = []
|
||||
for v in value:
|
||||
l.append(v.value)
|
||||
l.append(v.text)
|
||||
return l
|
||||
|
||||
class ListItemList(String):
|
||||
|
||||
"A radiolist/checklist item list parameter."
|
||||
|
||||
def __init__(self, name, status_first=0):
|
||||
String.__init__(self, name)
|
||||
self.status_first = status_first
|
||||
|
||||
def convert(self, value, program):
|
||||
l = []
|
||||
for v in value:
|
||||
boolean = Boolean(None)
|
||||
status = boolean.convert(v.status, program)
|
||||
if self.status_first:
|
||||
l += status
|
||||
l.append(v.value)
|
||||
l.append(v.text)
|
||||
if not self.status_first:
|
||||
l += status
|
||||
return l
|
||||
|
||||
# Dialogue argument values.
|
||||
|
||||
class MenuItem:
|
||||
|
||||
"A menu item which can also be used with radiolists and checklists."
|
||||
|
||||
def __init__(self, value, text, status=0):
|
||||
self.value = value
|
||||
self.text = text
|
||||
self.status = status
|
||||
|
||||
# Dialogue classes.
|
||||
|
||||
class Dialogue:
|
||||
|
||||
commands = {
|
||||
"KDE" : "kdialog",
|
||||
"GNOME" : "zenity",
|
||||
"MATE" : "zenity",
|
||||
"XFCE" : "zenity", # NOTE: Based on observations with Xubuntu.
|
||||
"X11" : "Xdialog"
|
||||
}
|
||||
|
||||
def open(self, desktop=None):
|
||||
|
||||
"""
|
||||
Open a dialogue box (dialog) using a program appropriate to the desktop
|
||||
environment in use.
|
||||
|
||||
If the optional 'desktop' parameter is specified then attempt to use
|
||||
that particular desktop environment's mechanisms to open the dialog
|
||||
instead of guessing or detecting which environment is being used.
|
||||
|
||||
Suggested values for 'desktop' are "standard", "KDE", "GNOME",
|
||||
"MATE", "Mac OS X", "Windows".
|
||||
|
||||
The result of the dialogue interaction may be a string indicating user
|
||||
input (for Input, Password, Menu, Pulldown), a list of strings
|
||||
indicating selections of one or more items (for RadioList, CheckList),
|
||||
or a value indicating true or false (for Question, Warning, Message,
|
||||
Error).
|
||||
|
||||
Where a string value may be expected but no choice is made, an empty
|
||||
string may be returned. Similarly, where a list of values is expected
|
||||
but no choice is made, an empty list may be returned.
|
||||
"""
|
||||
|
||||
# Decide on the desktop environment in use.
|
||||
|
||||
desktop_in_use = use_desktop(desktop)
|
||||
|
||||
# Get the program.
|
||||
|
||||
try:
|
||||
program = self.commands[desktop_in_use]
|
||||
except KeyError:
|
||||
raise OSError, "Desktop '%s' not supported (no known dialogue box command could be suggested)" % desktop_in_use
|
||||
|
||||
# The handler is one of the functions communicating with the subprocess.
|
||||
# Some handlers return boolean values, others strings.
|
||||
|
||||
handler, options = self.info[program]
|
||||
|
||||
cmd = [program]
|
||||
for option in options:
|
||||
if isinstance(option, str):
|
||||
cmd.append(option)
|
||||
else:
|
||||
value = getattr(self, option.name, None)
|
||||
cmd += option.convert(value, program)
|
||||
|
||||
return handler(cmd, 0)
|
||||
|
||||
class Simple(Dialogue):
|
||||
def __init__(self, text, width=None, height=None):
|
||||
self.text = text
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
class Question(Simple):
|
||||
|
||||
"""
|
||||
A dialogue asking a question and showing response buttons.
|
||||
Options: text, width (in characters), height (in characters)
|
||||
Response: a boolean value indicating an affirmative response (true) or a
|
||||
negative response
|
||||
"""
|
||||
|
||||
name = "question"
|
||||
info = {
|
||||
"kdialog" : (_status, ["--yesno", String("text")]),
|
||||
"zenity" : (_status, ["--question", StringKeyword("--text", "text")]),
|
||||
"Xdialog" : (_status, ["--stdout", "--yesno", String("text"), Integer("height"), Integer("width")]),
|
||||
}
|
||||
|
||||
class Warning(Simple):
|
||||
|
||||
"""
|
||||
A dialogue asking a question and showing response buttons.
|
||||
Options: text, width (in characters), height (in characters)
|
||||
Response: a boolean value indicating an affirmative response (true) or a
|
||||
negative response
|
||||
"""
|
||||
|
||||
name = "warning"
|
||||
info = {
|
||||
"kdialog" : (_status, ["--warningyesno", String("text")]),
|
||||
"zenity" : (_status, ["--warning", StringKeyword("--text", "text")]),
|
||||
"Xdialog" : (_status, ["--stdout", "--yesno", String("text"), Integer("height"), Integer("width")]),
|
||||
}
|
||||
|
||||
class Message(Simple):
|
||||
|
||||
"""
|
||||
A message dialogue.
|
||||
Options: text, width (in characters), height (in characters)
|
||||
Response: a boolean value indicating an affirmative response (true) or a
|
||||
negative response
|
||||
"""
|
||||
|
||||
name = "message"
|
||||
info = {
|
||||
"kdialog" : (_status, ["--msgbox", String("text")]),
|
||||
"zenity" : (_status, ["--info", StringKeyword("--text", "text")]),
|
||||
"Xdialog" : (_status, ["--stdout", "--msgbox", String("text"), Integer("height"), Integer("width")]),
|
||||
}
|
||||
|
||||
class Error(Simple):
|
||||
|
||||
"""
|
||||
An error dialogue.
|
||||
Options: text, width (in characters), height (in characters)
|
||||
Response: a boolean value indicating an affirmative response (true) or a
|
||||
negative response
|
||||
"""
|
||||
|
||||
name = "error"
|
||||
info = {
|
||||
"kdialog" : (_status, ["--error", String("text")]),
|
||||
"zenity" : (_status, ["--error", StringKeyword("--text", "text")]),
|
||||
"Xdialog" : (_status, ["--stdout", "--msgbox", String("text"), Integer("height"), Integer("width")]),
|
||||
}
|
||||
|
||||
class Menu(Simple):
|
||||
|
||||
"""
|
||||
A menu of options, one of which being selectable.
|
||||
Options: text, width (in characters), height (in characters),
|
||||
list_height (in items), items (MenuItem objects)
|
||||
Response: a value corresponding to the chosen item
|
||||
"""
|
||||
|
||||
name = "menu"
|
||||
info = {
|
||||
"kdialog" : (_readvalue(_readfrom), ["--menu", String("text"), MenuItemList("items")]),
|
||||
"zenity" : (_readvalue(_readfrom), ["--list", StringKeyword("--text", "text"), StringKeywords("--column", "titles"),
|
||||
MenuItemList("items")]
|
||||
),
|
||||
"Xdialog" : (_readvalue(_readfrom), ["--stdout", "--menubox",
|
||||
String("text"), Integer("height"), Integer("width"), Integer("list_height"), MenuItemList("items")]
|
||||
),
|
||||
}
|
||||
item = MenuItem
|
||||
number_of_titles = 2
|
||||
|
||||
def __init__(self, text, titles, items=None, width=None, height=None, list_height=None):
|
||||
|
||||
"""
|
||||
Initialise a menu with the given heading 'text', column 'titles', and
|
||||
optional 'items' (which may be added later), 'width' (in characters),
|
||||
'height' (in characters) and 'list_height' (in items).
|
||||
"""
|
||||
|
||||
Simple.__init__(self, text, width, height)
|
||||
self.titles = ([""] * self.number_of_titles + titles)[-self.number_of_titles:]
|
||||
self.items = items or []
|
||||
self.list_height = list_height
|
||||
|
||||
def add(self, *args, **kw):
|
||||
|
||||
"""
|
||||
Add an item, passing the given arguments to the appropriate item class.
|
||||
"""
|
||||
|
||||
self.items.append(self.item(*args, **kw))
|
||||
|
||||
class RadioList(Menu):
|
||||
|
||||
"""
|
||||
A list of radio buttons, one of which being selectable.
|
||||
Options: text, width (in characters), height (in characters),
|
||||
list_height (in items), items (MenuItem objects), titles
|
||||
Response: a list of values corresponding to chosen items (since some
|
||||
programs, eg. zenity, appear to support multiple default
|
||||
selections)
|
||||
"""
|
||||
|
||||
name = "radiolist"
|
||||
info = {
|
||||
"kdialog" : (_readvalues_kdialog(_readfrom), ["--radiolist", String("text"), ListItemList("items")]),
|
||||
"zenity" : (_readvalues_zenity(_readfrom),
|
||||
["--list", "--radiolist", StringKeyword("--text", "text"), StringKeywords("--column", "titles"),
|
||||
ListItemList("items", 1)]
|
||||
),
|
||||
"Xdialog" : (_readvalues_Xdialog(_readfrom), ["--stdout", "--radiolist",
|
||||
String("text"), Integer("height"), Integer("width"), Integer("list_height"), ListItemList("items")]
|
||||
),
|
||||
}
|
||||
number_of_titles = 3
|
||||
|
||||
class CheckList(Menu):
|
||||
|
||||
"""
|
||||
A list of checkboxes, many being selectable.
|
||||
Options: text, width (in characters), height (in characters),
|
||||
list_height (in items), items (MenuItem objects), titles
|
||||
Response: a list of values corresponding to chosen items
|
||||
"""
|
||||
|
||||
name = "checklist"
|
||||
info = {
|
||||
"kdialog" : (_readvalues_kdialog(_readfrom), ["--checklist", String("text"), ListItemList("items")]),
|
||||
"zenity" : (_readvalues_zenity(_readfrom),
|
||||
["--list", "--checklist", StringKeyword("--text", "text"), StringKeywords("--column", "titles"),
|
||||
ListItemList("items", 1)]
|
||||
),
|
||||
"Xdialog" : (_readvalues_Xdialog(_readfrom), ["--stdout", "--checklist",
|
||||
String("text"), Integer("height"), Integer("width"), Integer("list_height"), ListItemList("items")]
|
||||
),
|
||||
}
|
||||
number_of_titles = 3
|
||||
|
||||
class Pulldown(Menu):
|
||||
|
||||
"""
|
||||
A pull-down menu of options, one of which being selectable.
|
||||
Options: text, width (in characters), height (in characters),
|
||||
items (list of values)
|
||||
Response: a value corresponding to the chosen item
|
||||
"""
|
||||
|
||||
name = "pulldown"
|
||||
info = {
|
||||
"kdialog" : (_readvalue(_readfrom), ["--combobox", String("text"), Strings("items")]),
|
||||
"zenity" : (_readvalue(_readfrom),
|
||||
["--list", "--radiolist", StringKeyword("--text", "text"), StringKeywords("--column", "titles"),
|
||||
StringPairs("items")]
|
||||
),
|
||||
"Xdialog" : (_readvalue(_readfrom),
|
||||
["--stdout", "--combobox", String("text"), Integer("height"), Integer("width"), Strings("items")]),
|
||||
}
|
||||
item = unicode
|
||||
number_of_titles = 2
|
||||
|
||||
class Input(Simple):
|
||||
|
||||
"""
|
||||
An input dialogue, consisting of an input field.
|
||||
Options: text, input, width (in characters), height (in characters)
|
||||
Response: the text entered into the dialogue by the user
|
||||
"""
|
||||
|
||||
name = "input"
|
||||
info = {
|
||||
"kdialog" : (_readinput(_readfrom),
|
||||
["--inputbox", String("text"), String("data")]),
|
||||
"zenity" : (_readinput(_readfrom),
|
||||
["--entry", StringKeyword("--text", "text"), StringKeyword("--entry-text", "data")]),
|
||||
"Xdialog" : (_readinput(_readfrom),
|
||||
["--stdout", "--inputbox", String("text"), Integer("height"), Integer("width"), String("data")]),
|
||||
}
|
||||
|
||||
def __init__(self, text, data="", width=None, height=None):
|
||||
Simple.__init__(self, text, width, height)
|
||||
self.data = data
|
||||
|
||||
class Password(Input):
|
||||
|
||||
"""
|
||||
A password dialogue, consisting of a password entry field.
|
||||
Options: text, width (in characters), height (in characters)
|
||||
Response: the text entered into the dialogue by the user
|
||||
"""
|
||||
|
||||
name = "password"
|
||||
info = {
|
||||
"kdialog" : (_readinput(_readfrom),
|
||||
["--password", String("text")]),
|
||||
"zenity" : (_readinput(_readfrom),
|
||||
["--entry", StringKeyword("--text", "text"), "--hide-text"]),
|
||||
"Xdialog" : (_readinput(_readfrom),
|
||||
["--stdout", "--password", "--inputbox", String("text"), Integer("height"), Integer("width")]),
|
||||
}
|
||||
|
||||
class TextFile(Simple):
|
||||
|
||||
"""
|
||||
A text file input box.
|
||||
Options: filename, text, width (in characters), height (in characters)
|
||||
Response: any text returned by the dialogue program (typically an empty
|
||||
string)
|
||||
"""
|
||||
|
||||
name = "textfile"
|
||||
info = {
|
||||
"kdialog" : (_readfrom, ["--textbox", String("filename"), Integer("width", pixels=1), Integer("height", pixels=1)]),
|
||||
"zenity" : (_readfrom, ["--text-info", StringKeyword("--filename", "filename"), IntegerKeyword("--width", "width", pixels=1),
|
||||
IntegerKeyword("--height", "height", pixels=1)]
|
||||
),
|
||||
"Xdialog" : (_readfrom, ["--stdout", "--textbox", String("filename"), Integer("height"), Integer("width")]),
|
||||
}
|
||||
|
||||
def __init__(self, filename, text="", width=None, height=None):
|
||||
Simple.__init__(self, text, width, height)
|
||||
self.filename = filename
|
||||
|
||||
# Available dialogues.
|
||||
|
||||
available = [Question, Warning, Message, Error, Menu, CheckList, RadioList, Input, Password, Pulldown, TextFile]
|
||||
|
||||
# Supported desktop environments.
|
||||
|
||||
supported = Dialogue.commands.keys()
|
||||
|
||||
# vim: tabstop=4 expandtab shiftwidth=4
|
@@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Simple desktop window enumeration for Python.
|
||||
|
||||
Copyright (C) 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the Free
|
||||
Software Foundation; either version 3 of the License, or (at your option) any
|
||||
later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
--------
|
||||
|
||||
Finding Open Windows on the Desktop
|
||||
-----------------------------------
|
||||
|
||||
To obtain a list of windows, use the desktop.windows.list function as follows:
|
||||
|
||||
windows = desktop.windows.list()
|
||||
|
||||
To obtain the root window, typically the desktop background, use the
|
||||
desktop.windows.root function as follows:
|
||||
|
||||
root = desktop.windows.root()
|
||||
|
||||
Each window object can be inspected through a number of methods. For example:
|
||||
|
||||
name = window.name()
|
||||
width, height = window.size()
|
||||
x, y = window.position()
|
||||
child_windows = window.children()
|
||||
|
||||
See the desktop.windows.Window class for more information.
|
||||
"""
|
||||
|
||||
from desktop import _is_x11, _get_x11_vars, _readfrom, use_desktop
|
||||
import re
|
||||
|
||||
# System functions.
|
||||
|
||||
def _xwininfo(identifier, action):
|
||||
if identifier is None:
|
||||
args = "-root"
|
||||
else:
|
||||
args = "-id " + identifier
|
||||
|
||||
s = _readfrom(_get_x11_vars() + "xwininfo %s -%s" % (args, action), shell=1)
|
||||
|
||||
# Return a mapping of keys to values for the "stats" action.
|
||||
|
||||
if action == "stats":
|
||||
d = {}
|
||||
for line in s.split("\n"):
|
||||
fields = line.split(":")
|
||||
if len(fields) < 2:
|
||||
continue
|
||||
key, value = fields[0].strip(), ":".join(fields[1:]).strip()
|
||||
d[key] = value
|
||||
|
||||
return d
|
||||
|
||||
# Otherwise, return the raw output.
|
||||
|
||||
else:
|
||||
return s
|
||||
|
||||
def _get_int_properties(d, properties):
|
||||
results = []
|
||||
for property in properties:
|
||||
results.append(int(d[property]))
|
||||
return results
|
||||
|
||||
# Finder functions.
|
||||
|
||||
def find_all(name):
|
||||
return 1
|
||||
|
||||
def find_named(name):
|
||||
return name is not None
|
||||
|
||||
def find_by_name(name):
|
||||
return lambda n, t=name: n == t
|
||||
|
||||
# Window classes.
|
||||
# NOTE: X11 is the only supported desktop so far.
|
||||
|
||||
class Window:
|
||||
|
||||
"A window on the desktop."
|
||||
|
||||
_name_pattern = re.compile(r':\s+\(.*?\)\s+[-0-9x+]+\s+[-0-9+]+$')
|
||||
_absent_names = "(has no name)", "(the root window) (has no name)"
|
||||
|
||||
def __init__(self, identifier):
|
||||
|
||||
"Initialise the window with the given 'identifier'."
|
||||
|
||||
self.identifier = identifier
|
||||
|
||||
# Finder methods (from above).
|
||||
|
||||
self.find_all = find_all
|
||||
self.find_named = find_named
|
||||
self.find_by_name = find_by_name
|
||||
|
||||
def __repr__(self):
|
||||
return "Window(%r)" % self.identifier
|
||||
|
||||
# Methods which deal with the underlying commands.
|
||||
|
||||
def _get_handle_and_name(self, text):
|
||||
fields = text.strip().split(" ")
|
||||
handle = fields[0]
|
||||
|
||||
# Get the "<name>" part, stripping off the quotes.
|
||||
|
||||
name = " ".join(fields[1:])
|
||||
if len(name) > 1 and name[0] == '"' and name[-1] == '"':
|
||||
name = name[1:-1]
|
||||
|
||||
if name in self._absent_names:
|
||||
return handle, None
|
||||
else:
|
||||
return handle, name
|
||||
|
||||
def _get_this_handle_and_name(self, line):
|
||||
fields = line.split(":")
|
||||
return self._get_handle_and_name(":".join(fields[1:]))
|
||||
|
||||
def _get_descendant_handle_and_name(self, line):
|
||||
match = self._name_pattern.search(line)
|
||||
if match:
|
||||
return self._get_handle_and_name(line[:match.start()].strip())
|
||||
else:
|
||||
raise OSError, "Window information from %r did not contain window details." % line
|
||||
|
||||
def _descendants(self, s, fn):
|
||||
handles = []
|
||||
adding = 0
|
||||
for line in s.split("\n"):
|
||||
if line.endswith("child:") or line.endswith("children:"):
|
||||
if not adding:
|
||||
adding = 1
|
||||
elif adding and line:
|
||||
handle, name = self._get_descendant_handle_and_name(line)
|
||||
if fn(name):
|
||||
handles.append(handle)
|
||||
return [Window(handle) for handle in handles]
|
||||
|
||||
# Public methods.
|
||||
|
||||
def children(self, all=0):
|
||||
|
||||
"""
|
||||
Return a list of windows which are children of this window. If the
|
||||
optional 'all' parameter is set to a true value, all such windows will
|
||||
be returned regardless of whether they have any name information.
|
||||
"""
|
||||
|
||||
s = _xwininfo(self.identifier, "children")
|
||||
return self._descendants(s, all and self.find_all or self.find_named)
|
||||
|
||||
def descendants(self, all=0):
|
||||
|
||||
"""
|
||||
Return a list of windows which are descendants of this window. If the
|
||||
optional 'all' parameter is set to a true value, all such windows will
|
||||
be returned regardless of whether they have any name information.
|
||||
"""
|
||||
|
||||
s = _xwininfo(self.identifier, "tree")
|
||||
return self._descendants(s, all and self.find_all or self.find_named)
|
||||
|
||||
def find(self, callable):
|
||||
|
||||
"""
|
||||
Return windows using the given 'callable' (returning a true or a false
|
||||
value when invoked with a window name) for descendants of this window.
|
||||
"""
|
||||
|
||||
s = _xwininfo(self.identifier, "tree")
|
||||
return self._descendants(s, callable)
|
||||
|
||||
def name(self):
|
||||
|
||||
"Return the name of the window."
|
||||
|
||||
d = _xwininfo(self.identifier, "stats")
|
||||
|
||||
# Format is 'xwininfo: Window id: <handle> "<name>"
|
||||
|
||||
return self._get_this_handle_and_name(d["xwininfo"])[1]
|
||||
|
||||
def size(self):
|
||||
|
||||
"Return a tuple containing the width and height of this window."
|
||||
|
||||
d = _xwininfo(self.identifier, "stats")
|
||||
return _get_int_properties(d, ["Width", "Height"])
|
||||
|
||||
def position(self):
|
||||
|
||||
"Return a tuple containing the upper left co-ordinates of this window."
|
||||
|
||||
d = _xwininfo(self.identifier, "stats")
|
||||
return _get_int_properties(d, ["Absolute upper-left X", "Absolute upper-left Y"])
|
||||
|
||||
def displayed(self):
|
||||
|
||||
"""
|
||||
Return whether the window is displayed in some way (but not necessarily
|
||||
visible on the current screen).
|
||||
"""
|
||||
|
||||
d = _xwininfo(self.identifier, "stats")
|
||||
return d["Map State"] != "IsUnviewable"
|
||||
|
||||
def visible(self):
|
||||
|
||||
"Return whether the window is displayed and visible."
|
||||
|
||||
d = _xwininfo(self.identifier, "stats")
|
||||
return d["Map State"] == "IsViewable"
|
||||
|
||||
def list(desktop=None):
|
||||
|
||||
"""
|
||||
Return a list of windows for the current desktop. If the optional 'desktop'
|
||||
parameter is specified then attempt to use that particular desktop
|
||||
environment's mechanisms to look for windows.
|
||||
"""
|
||||
|
||||
root_window = root(desktop)
|
||||
window_list = [window for window in root_window.descendants() if window.displayed()]
|
||||
window_list.insert(0, root_window)
|
||||
return window_list
|
||||
|
||||
def root(desktop=None):
|
||||
|
||||
"""
|
||||
Return the root window for the current desktop. If the optional 'desktop'
|
||||
parameter is specified then attempt to use that particular desktop
|
||||
environment's mechanisms to look for windows.
|
||||
"""
|
||||
|
||||
# NOTE: The desktop parameter is currently ignored and X11 is tested for
|
||||
# NOTE: directly.
|
||||
|
||||
if _is_x11():
|
||||
return Window(None)
|
||||
else:
|
||||
raise OSError, "Desktop '%s' not supported" % use_desktop(desktop)
|
||||
|
||||
def find(callable, desktop=None):
|
||||
|
||||
"""
|
||||
Find and return windows using the given 'callable' for the current desktop.
|
||||
If the optional 'desktop' parameter is specified then attempt to use that
|
||||
particular desktop environment's mechanisms to look for windows.
|
||||
"""
|
||||
|
||||
return root(desktop).find(callable)
|
||||
|
||||
# vim: tabstop=4 expandtab shiftwidth=4
|
@@ -0,0 +1,364 @@
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
|
||||
body {
|
||||
background-color: white;
|
||||
border-radius: 3px;
|
||||
border: 3px solid #EEE;
|
||||
box-shadow: inset 0 0 0 1px #CECECE;
|
||||
font-family: Helvetica,arial,sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
width: 975px;
|
||||
padding: 30px;
|
||||
margin: 2em auto; }
|
||||
|
||||
body > *:first-child {
|
||||
margin-top: 0 !important; }
|
||||
body > *:last-child {
|
||||
margin-bottom: 0 !important; }
|
||||
|
||||
a {
|
||||
color: #4183C4;
|
||||
text-decoration: none; }
|
||||
a:hover {
|
||||
text-decoration: underline; }
|
||||
a.absent {
|
||||
color: #cc0000; }
|
||||
a.anchor {
|
||||
display: block;
|
||||
padding-left: 30px;
|
||||
margin-left: -30px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0; }
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 20px 0 10px;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
cursor: text;
|
||||
position: relative; }
|
||||
|
||||
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
|
||||
background: url("../../images/modules/styleguide/para.png") no-repeat 10px center;
|
||||
text-decoration: none; }
|
||||
|
||||
h1 tt, h1 code {
|
||||
font-size: inherit; }
|
||||
|
||||
h2 tt, h2 code {
|
||||
font-size: inherit; }
|
||||
|
||||
h3 tt, h3 code {
|
||||
font-size: inherit; }
|
||||
|
||||
h4 tt, h4 code {
|
||||
font-size: inherit; }
|
||||
|
||||
h5 tt, h5 code {
|
||||
font-size: inherit; }
|
||||
|
||||
h6 tt, h6 code {
|
||||
font-size: inherit; }
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
color: black; }
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
color: black; }
|
||||
|
||||
h3 {
|
||||
font-size: 18px; }
|
||||
|
||||
h4 {
|
||||
font-size: 16px; }
|
||||
|
||||
h5 {
|
||||
font-size: 14px; }
|
||||
|
||||
h6 {
|
||||
color: #777777;
|
||||
font-size: 14px; }
|
||||
|
||||
p, blockquote, ul, ol, dl, li, table, pre {
|
||||
margin: 15px 0; }
|
||||
|
||||
hr {
|
||||
background: transparent url("https://a248.e.akamai.net/assets.github.com/assets/primer/markdown/dirty-shade-0e7d81b119cc9beae17b0c98093d121fa0050a74.png") repeat-x 0 0;
|
||||
border: 0 none;
|
||||
color: #ccc;
|
||||
height: 4px;
|
||||
padding: 0; }
|
||||
|
||||
body > h2:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0; }
|
||||
body > h1:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0; }
|
||||
body > h1:first-child + h2 {
|
||||
margin-top: 0;
|
||||
padding-top: 0; }
|
||||
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0; }
|
||||
|
||||
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
|
||||
margin-top: 0;
|
||||
padding-top: 0; }
|
||||
|
||||
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
|
||||
margin-top: 0; }
|
||||
|
||||
li p.first {
|
||||
display: inline-block; }
|
||||
|
||||
ul, ol {
|
||||
padding-left: 30px; }
|
||||
|
||||
ul :first-child, ol :first-child {
|
||||
margin-top: 0; }
|
||||
|
||||
ul :last-child, ol :last-child {
|
||||
margin-bottom: 0; }
|
||||
|
||||
dl {
|
||||
padding: 0; }
|
||||
dl dt {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
padding: 0;
|
||||
margin: 15px 0 5px; }
|
||||
dl dt:first-child {
|
||||
padding: 0; }
|
||||
dl dt > :first-child {
|
||||
margin-top: 0; }
|
||||
dl dt > :last-child {
|
||||
margin-bottom: 0; }
|
||||
dl dd {
|
||||
margin: 0 0 15px;
|
||||
padding: 0 15px; }
|
||||
dl dd > :first-child {
|
||||
margin-top: 0; }
|
||||
dl dd > :last-child {
|
||||
margin-bottom: 0; }
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #dddddd;
|
||||
padding: 0 15px;
|
||||
color: #777777; }
|
||||
blockquote > :first-child {
|
||||
margin-top: 0; }
|
||||
blockquote > :last-child {
|
||||
margin-bottom: 0; }
|
||||
|
||||
table {
|
||||
border-collapse: collapse; border-spacing: 0; padding: 0; }
|
||||
table tr {
|
||||
border-top: 1px solid #cccccc;
|
||||
background-color: white;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
table tr:nth-child(2n) {
|
||||
background-color: #f8f8f8; }
|
||||
table tr th {
|
||||
font-weight: bold;
|
||||
border: 1px solid #cccccc;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
padding: 6px 13px; }
|
||||
table tr td {
|
||||
border: 1px solid #cccccc;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
padding: 6px 13px; }
|
||||
table tr th :first-child, table tr td :first-child {
|
||||
margin-top: 0; }
|
||||
table tr th :last-child, table tr td :last-child {
|
||||
margin-bottom: 0; }
|
||||
|
||||
img {
|
||||
max-width: 100%; }
|
||||
|
||||
span.frame {
|
||||
display: block;
|
||||
overflow: hidden; }
|
||||
span.frame > span {
|
||||
border: 1px solid #dddddd;
|
||||
display: block;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
margin: 13px 0 0;
|
||||
padding: 7px;
|
||||
width: auto; }
|
||||
span.frame span img {
|
||||
display: block;
|
||||
float: left; }
|
||||
span.frame span span {
|
||||
clear: both;
|
||||
color: #333333;
|
||||
display: block;
|
||||
padding: 5px 0 0; }
|
||||
span.align-center {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
clear: both; }
|
||||
span.align-center > span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 13px auto 0;
|
||||
text-align: center; }
|
||||
span.align-center span img {
|
||||
margin: 0 auto;
|
||||
text-align: center; }
|
||||
span.align-right {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
clear: both; }
|
||||
span.align-right > span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 13px 0 0;
|
||||
text-align: right; }
|
||||
span.align-right span img {
|
||||
margin: 0;
|
||||
text-align: right; }
|
||||
span.float-left {
|
||||
display: block;
|
||||
margin-right: 13px;
|
||||
overflow: hidden;
|
||||
float: left; }
|
||||
span.float-left span {
|
||||
margin: 13px 0 0; }
|
||||
span.float-right {
|
||||
display: block;
|
||||
margin-left: 13px;
|
||||
overflow: hidden;
|
||||
float: right; }
|
||||
span.float-right > span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 13px auto 0;
|
||||
text-align: right; }
|
||||
|
||||
code, tt {
|
||||
margin: 0 2px;
|
||||
padding: 0 5px;
|
||||
white-space: nowrap;
|
||||
border: 1px solid #eaeaea;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 3px; }
|
||||
|
||||
pre code {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
white-space: pre;
|
||||
border: none;
|
||||
background: transparent; }
|
||||
|
||||
.highlight pre {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #cccccc;
|
||||
font-size: 13px;
|
||||
line-height: 19px;
|
||||
overflow: auto;
|
||||
padding: 6px 10px;
|
||||
border-radius: 3px; }
|
||||
|
||||
pre {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #cccccc;
|
||||
font-size: 13px;
|
||||
line-height: 19px;
|
||||
overflow: auto;
|
||||
padding: 6px 10px;
|
||||
border-radius: 3px; }
|
||||
pre code, pre tt {
|
||||
background-color: transparent;
|
||||
border: none; }
|
||||
|
||||
|
||||
.markdown-body code,.markdown-body tt{margin:0 2px;padding:0px 5px;white-space:nowrap;border:1px solid #eaeaea;background-color:#f8f8f8;border-radius:3px}
|
||||
.markdown-body pre>code{margin:0;padding:0;white-space:pre;border:none;background:transparent}
|
||||
.markdown-body .highlight pre,.markdown-body pre{background-color:#f8f8f8;border:1px solid #ccc;font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px}
|
||||
.markdown-body pre code,.markdown-body pre tt{background-color:transparent;border:none}
|
||||
.highlight{background:#ffffff}
|
||||
.highlight .c{color:#999988;font-style:italic}
|
||||
.highlight .err{color:#a61717;background-color:#e3d2d2}
|
||||
.highlight .k{font-weight:bold}
|
||||
.highlight .o{font-weight:bold}
|
||||
.highlight .cm{color:#999988;font-style:italic}
|
||||
.highlight .cp{color:#999999;font-weight:bold}
|
||||
.highlight .c1{color:#999988;font-style:italic}
|
||||
.highlight .cs{color:#999999;font-weight:bold;font-style:italic}
|
||||
.highlight .gd{color:#000000;background-color:#ffdddd}
|
||||
.highlight .gd .x{color:#000000;background-color:#ffaaaa}
|
||||
.highlight .ge{font-style:italic}
|
||||
.highlight .gr{color:#aa0000}
|
||||
.highlight .gh{color:#999999}
|
||||
.highlight .gi{color:#000000;background-color:#ddffdd}
|
||||
.highlight .gi .x{color:#000000;background-color:#aaffaa}
|
||||
.highlight .go{color:#888888}
|
||||
.highlight .gp{color:#555555}
|
||||
.highlight .gs{font-weight:bold}
|
||||
.highlight .gu{color:#800080;font-weight:bold}
|
||||
.highlight .gt{color:#aa0000}
|
||||
.highlight .kc{font-weight:bold}
|
||||
.highlight .kd{font-weight:bold}
|
||||
.highlight .kn{font-weight:bold}
|
||||
.highlight .kp{font-weight:bold}
|
||||
.highlight .kr{font-weight:bold}
|
||||
.highlight .kt{color:#445588;font-weight:bold}
|
||||
.highlight .m{color:#009999}
|
||||
.highlight .s{color:#d14}
|
||||
.highlight .na{color:#008080}
|
||||
.highlight .nb{color:#0086B3}
|
||||
.highlight .nc{color:#445588;font-weight:bold}
|
||||
.highlight .no{color:#008080}
|
||||
.highlight .ni{color:#800080}
|
||||
.highlight .ne{color:#990000;font-weight:bold}
|
||||
.highlight .nf{color:#990000;font-weight:bold}
|
||||
.highlight .nn{color:#555555}
|
||||
.highlight .nt{color:#000080}
|
||||
.highlight .nv{color:#008080}
|
||||
.highlight .ow{font-weight:bold}
|
||||
.highlight .w{color:#bbbbbb}
|
||||
.highlight .mf{color:#009999}
|
||||
.highlight .mh{color:#009999}
|
||||
.highlight .mi{color:#009999}
|
||||
.highlight .mo{color:#009999}
|
||||
.highlight .sb{color:#d14}
|
||||
.highlight .sc{color:#d14}
|
||||
.highlight .sd{color:#d14}
|
||||
.highlight .s2{color:#d14}
|
||||
.highlight .se{color:#d14}
|
||||
.highlight .sh{color:#d14}
|
||||
.highlight .si{color:#d14}
|
||||
.highlight .sx{color:#d14}
|
||||
.highlight .sr{color:#009926}
|
||||
.highlight .s1{color:#d14}
|
||||
.highlight .ss{color:#990073}
|
||||
.highlight .bp{color:#999999}
|
||||
.highlight .vc{color:#008080}
|
||||
.highlight .vg{color:#008080}
|
||||
.highlight .vi{color:#008080}
|
||||
.highlight .il{color:#009999}
|
||||
.highlight .gc{color:#999;background-color:#EAF2F5}
|
||||
.type-csharp .highlight .k{color:#0000FF}
|
||||
.type-csharp .highlight .kt{color:#0000FF}
|
||||
.type-csharp .highlight .nf{color:#000000;font-weight:normal}
|
||||
.type-csharp .highlight .nc{color:#2B91AF}
|
||||
.type-csharp .highlight .nn{color:#000000}
|
||||
.type-csharp .highlight .s{color:#A31515}
|
||||
.type-csharp .highlight .sc{color:#A31515}
|
||||
|
||||
|
@@ -0,0 +1,122 @@
|
||||
html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
|
||||
|
||||
body{
|
||||
color:#444;
|
||||
font-family:Georgia, Palatino, 'Palatino Linotype', Times, 'Times New Roman',
|
||||
"Hiragino Sans GB", "STXihei", "微软雅黑", serif;
|
||||
font-size:12px;
|
||||
line-height:1.5em;
|
||||
background:#fefefe;
|
||||
width: 45em;
|
||||
margin: 10px auto;
|
||||
padding: 1em;
|
||||
outline: 1300px solid #FAFAFA;
|
||||
}
|
||||
|
||||
a{ color: #0645ad; text-decoration:none;}
|
||||
a:visited{ color: #0b0080; }
|
||||
a:hover{ color: #06e; }
|
||||
a:active{ color:#faa700; }
|
||||
a:focus{ outline: thin dotted; }
|
||||
a:hover, a:active{ outline: 0; }
|
||||
|
||||
span.backtick {
|
||||
border:1px solid #EAEAEA;
|
||||
border-radius:3px;
|
||||
background:#F8F8F8;
|
||||
padding:0 3px 0 3px;
|
||||
}
|
||||
|
||||
::-moz-selection{background:rgba(255,255,0,0.3);color:#000}
|
||||
::selection{background:rgba(255,255,0,0.3);color:#000}
|
||||
|
||||
a::-moz-selection{background:rgba(255,255,0,0.3);color:#0645ad}
|
||||
a::selection{background:rgba(255,255,0,0.3);color:#0645ad}
|
||||
|
||||
p{
|
||||
margin:1em 0;
|
||||
}
|
||||
|
||||
img{
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,h6{
|
||||
font-weight:normal;
|
||||
color:#111;
|
||||
line-height:1em;
|
||||
}
|
||||
h4,h5,h6{ font-weight: bold; }
|
||||
h1{ font-size:2.5em; }
|
||||
h2{ font-size:2em; border-bottom:1px solid silver; padding-bottom: 5px; }
|
||||
h3{ font-size:1.5em; }
|
||||
h4{ font-size:1.2em; }
|
||||
h5{ font-size:1em; }
|
||||
h6{ font-size:0.9em; }
|
||||
|
||||
blockquote{
|
||||
color:#666666;
|
||||
margin:0;
|
||||
padding-left: 3em;
|
||||
border-left: 0.5em #EEE solid;
|
||||
}
|
||||
hr { display: block; height: 2px; border: 0; border-top: 1px solid #aaa;border-bottom: 1px solid #eee; margin: 1em 0; padding: 0; }
|
||||
|
||||
|
||||
pre , code, kbd, samp {
|
||||
color: #000;
|
||||
font-family: monospace;
|
||||
font-size: 0.88em;
|
||||
border-radius:3px;
|
||||
background-color: #F8F8F8;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; padding: 5px 12px;}
|
||||
pre code { border: 0px !important; padding: 0;}
|
||||
code { padding: 0 3px 0 3px; }
|
||||
|
||||
b, strong { font-weight: bold; }
|
||||
|
||||
dfn { font-style: italic; }
|
||||
|
||||
ins { background: #ff9; color: #000; text-decoration: none; }
|
||||
|
||||
mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }
|
||||
|
||||
sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
|
||||
sup { top: -0.5em; }
|
||||
sub { bottom: -0.25em; }
|
||||
|
||||
ul, ol { margin: 1em 0; padding: 0 0 0 2em; }
|
||||
li p:last-child { margin:0 }
|
||||
dd { margin: 0 0 0 2em; }
|
||||
|
||||
img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
|
||||
|
||||
table { border-collapse: collapse; border-spacing: 0; }
|
||||
td { vertical-align: top; }
|
||||
|
||||
@media only screen and (min-width: 480px) {
|
||||
body{font-size:14px;}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
body{font-size:16px;}
|
||||
}
|
||||
|
||||
@media print {
|
||||
* { background: transparent !important; color: black !important; filter:none !important; -ms-filter: none !important; }
|
||||
body{font-size:12pt; max-width:100%; outline:none;}
|
||||
a, a:visited { text-decoration: underline; }
|
||||
hr { height: 1px; border:0; border-bottom:1px solid black; }
|
||||
a[href]:after { content: " (" attr(href) ")"; }
|
||||
abbr[title]:after { content: " (" attr(title) ")"; }
|
||||
.ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; }
|
||||
pre, blockquote { border: 1px solid #999; padding-right: 1em; page-break-inside: avoid; }
|
||||
tr, img { page-break-inside: avoid; }
|
||||
img { max-width: 100% !important; }
|
||||
@page :left { margin: 15mm 20mm 15mm 10mm; }
|
||||
@page :right { margin: 15mm 10mm 15mm 20mm; }
|
||||
p, h2, h3 { orphans: 3; widows: 3; }
|
||||
h2, h3 { page-break-after: avoid; }
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
{"url": "https://github.com/revolunet/sublimetext-markdown-preview", "version": "2013.04.01.18.50.45", "description": "markdown preview plugin for sublime text 2"}
|
@@ -0,0 +1,141 @@
|
||||
<h1 id="sample-markdown-cheat-sheet">Sample Markdown Cheat Sheet</h1>
|
||||
|
||||
<p>This is a sample markdown file to help you write Markdown quickly :)</p>
|
||||
|
||||
<p>If you use the fabulous <a href="http://sublimetext.com">Sublime Text 2 editor</a> along with the <a href="https://github.com/revolunet/sublimetext-markdown-preview">Markdown Preview plugin</a>, open your ST2 Palette with <code>CMD+P</code> then choose <code>Markdown Preview in browser</code> to see the result in your browser.</p>
|
||||
|
||||
<h2 id="text-basics">Text basics</h2>
|
||||
|
||||
<p>this is <em>italic</em> and this is <strong>bold</strong> . another <em>italic</em> and another <strong>bold</strong></p>
|
||||
|
||||
<p>this is <code>important</code> text. and percentage signs : % and <code>%</code></p>
|
||||
|
||||
<p>This is a paragraph with a footnote (builtin parser only). <sup class="footnote-ref" id="fnref-note-id"><a href="#fn-note-id">1</a></sup> </p>
|
||||
|
||||
<p>Insert <code>[ toc ]</code> without spaces to generate a table of contents (builtin parser only).</p>
|
||||
|
||||
<h2 id="indentation">Indentation</h2>
|
||||
|
||||
<blockquote>
|
||||
<p>Here is some indented text</p>
|
||||
|
||||
<blockquote>
|
||||
<p>even more indented</p>
|
||||
</blockquote>
|
||||
</blockquote>
|
||||
|
||||
<h2 id="titles">Titles</h2>
|
||||
|
||||
<h1 id="big-title-h1">Big title (h1)</h1>
|
||||
|
||||
<h2 id="middle-title-h2">Middle title (h2)</h2>
|
||||
|
||||
<h3 id="smaller-title-h3">Smaller title (h3)</h3>
|
||||
|
||||
<h4 id="and-so-on-hx">and so on (hX)</h4>
|
||||
|
||||
<h5 id="and-so-on-hx-2">and so on (hX)</h5>
|
||||
|
||||
<h6 id="and-so-on-hx-3">and so on (hX)</h6>
|
||||
|
||||
<h2 id="example-lists-1">Example lists (1)</h2>
|
||||
|
||||
<ul>
|
||||
<li>bullets can be <code>-</code>, <code>+</code>, or <code>*</code></li>
|
||||
<li>bullet list 1</li>
|
||||
<li><p>bullet list 2</p>
|
||||
|
||||
<ul>
|
||||
<li>sub item 1</li>
|
||||
<li><p>sub item 2</p>
|
||||
|
||||
<p>with indented text inside</p></li>
|
||||
</ul></li>
|
||||
<li><p>bullet list 3</p></li>
|
||||
<li>bullet list 4</li>
|
||||
<li>bullet list 5</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="links">Links</h2>
|
||||
|
||||
<p>This is an <a href="http://lmgtfy.com/">example inline link</a> and <a href="http://lmgtfy.com/" title="Hello, world">another one with a title</a>.</p>
|
||||
|
||||
<p>Links can also be reference based : <a href="http://revolunet.com">reference 1</a> or <a href="http://revolunet.com" title="rich web apps">reference 2 with title</a>.</p>
|
||||
|
||||
<p>References are usually placed at the bottom of the document</p>
|
||||
|
||||
<h2 id="images">Images</h2>
|
||||
|
||||
<p>A sample image :</p>
|
||||
|
||||
<p><img src="http://www.revolunet.com/static/parisjs8/img/logo-revolunet-carre.jpg" alt="revolunet logo" title="revolunet logo" /></p>
|
||||
|
||||
<p>As links, images can also use references instead of inline links :</p>
|
||||
|
||||
<p><img src="http://www.revolunet.com/static/parisjs8/img/logo-revolunet-carre.jpg" alt="revolunet logo" title="revolunet logo" /></p>
|
||||
|
||||
<h2 id="code">Code</h2>
|
||||
|
||||
<p>It's quite easy to show code in markdown files.</p>
|
||||
|
||||
<p>Backticks can be used to <code>highlight</code> some words.</p>
|
||||
|
||||
<p>Also, any indented block is considered a code block.</p>
|
||||
|
||||
<pre><code><script>
|
||||
document.location = 'http://lmgtfy.com/?q=markdown+cheat+sheet';
|
||||
</script>
|
||||
</code></pre>
|
||||
|
||||
<h2 id="github-flavored-markdown">GitHub Flavored Markdown</h2>
|
||||
|
||||
<p>If you use the Github parser, you can use some of <a href="http://github.github.com/github-flavored-markdown/">Github Flavored Markdown</a> syntax :</p>
|
||||
|
||||
<ul>
|
||||
<li>User/Project@SHA: revolunet/sublimetext-markdown-preview@7da61badeda468b5019869d11000307e07e07401</li>
|
||||
<li>User/Project#Issue: revolunet/sublimetext-markdown-preview#1</li>
|
||||
<li>User : @revolunet</li>
|
||||
</ul>
|
||||
|
||||
<p>Some Python code :</p>
|
||||
|
||||
<pre><code>import random
|
||||
|
||||
class CardGame(object):
|
||||
""" a sample python class """
|
||||
NB_CARDS = 32
|
||||
def __init__(self, cards=5):
|
||||
self.cards = random.sample(range(self.NB_CARDS), 5)
|
||||
print 'ready to play'
|
||||
</code></pre>
|
||||
|
||||
<p>Some Javascript code :</p>
|
||||
|
||||
<pre><code>var config = {
|
||||
duration: 5,
|
||||
comment: 'WTF'
|
||||
}
|
||||
// callbacks beauty un action
|
||||
async_call('/path/to/api', function(json) {
|
||||
another_call(json, function(result2) {
|
||||
another_another_call(result2, function(result3) {
|
||||
another_another_another_call(result3, function(result4) {
|
||||
alert('And if all went well, i got my result :)');
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
</code></pre>
|
||||
|
||||
<h2 id="about">About</h2>
|
||||
|
||||
<p>This plugin and this sample file is proudly brought to you by the <a href="http://revolunet.com">revolunet team</a></p>
|
||||
|
||||
<div class="footnotes">
|
||||
<hr />
|
||||
<ol>
|
||||
<li id="fn-note-id">
|
||||
<p>This is the text of the note. <a href="#fnref-note-id" class="footnoteBackLink" title="Jump back to footnote 1 in the text.">↩</a></p>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
@@ -0,0 +1,132 @@
|
||||
Sample Markdown Cheat Sheet
|
||||
===========================
|
||||
|
||||
This is a sample markdown file to help you write Markdown quickly :)
|
||||
|
||||
If you use the fabulous [Sublime Text 2 editor][ST2] along with the [Markdown Preview plugin][MarkdownPreview], open your ST2 Palette with `CMD+P` then choose `Markdown Preview in browser` to see the result in your browser.
|
||||
|
||||
## Text basics
|
||||
this is *italic* and this is **bold** . another _italic_ and another __bold__
|
||||
|
||||
this is `important` text. and percentage signs : % and `%`
|
||||
|
||||
This is a paragraph with a footnote (builtin parser only). [^note-id]
|
||||
|
||||
Insert `[ toc ]` without spaces to generate a table of contents (builtin parser only).
|
||||
|
||||
## Indentation
|
||||
> Here is some indented text
|
||||
>> even more indented
|
||||
|
||||
## Titles
|
||||
# Big title (h1)
|
||||
## Middle title (h2)
|
||||
### Smaller title (h3)
|
||||
#### and so on (hX)
|
||||
##### and so on (hX)
|
||||
###### and so on (hX)
|
||||
|
||||
## Example lists (1)
|
||||
|
||||
- bullets can be `-`, `+`, or `*`
|
||||
- bullet list 1
|
||||
- bullet list 2
|
||||
- sub item 1
|
||||
- sub item 2
|
||||
|
||||
with indented text inside
|
||||
|
||||
- bullet list 3
|
||||
+ bullet list 4
|
||||
* bullet list 5
|
||||
|
||||
## Links
|
||||
|
||||
This is an [example inline link](http://lmgtfy.com/) and [another one with a title](http://lmgtfy.com/ "Hello, world").
|
||||
|
||||
Links can also be reference based : [reference 1][ref1] or [reference 2 with title][ref2].
|
||||
|
||||
References are usually placed at the bottom of the document
|
||||
|
||||
## Images
|
||||
|
||||
A sample image :
|
||||
|
||||

|
||||
|
||||
As links, images can also use references instead of inline links :
|
||||
|
||||
![revolunet logo][revolunet-logo]
|
||||
|
||||
|
||||
## Code
|
||||
|
||||
It's quite easy to show code in markdown files.
|
||||
|
||||
Backticks can be used to `highlight` some words.
|
||||
|
||||
Also, any indented block is considered a code block.
|
||||
|
||||
<script>
|
||||
document.location = 'http://lmgtfy.com/?q=markdown+cheat+sheet';
|
||||
</script>
|
||||
|
||||
## GitHub Flavored Markdown
|
||||
|
||||
If you use the Github parser, you can use some of [Github Flavored Markdown][gfm] syntax :
|
||||
|
||||
* User/Project@SHA: revolunet/sublimetext-markdown-preview@7da61badeda468b5019869d11000307e07e07401
|
||||
* User/Project#Issue: revolunet/sublimetext-markdown-preview#1
|
||||
* User : @revolunet
|
||||
|
||||
Some Python code :
|
||||
|
||||
```python
|
||||
import random
|
||||
|
||||
class CardGame(object):
|
||||
""" a sample python class """
|
||||
NB_CARDS = 32
|
||||
def __init__(self, cards=5):
|
||||
self.cards = random.sample(range(self.NB_CARDS), 5)
|
||||
print 'ready to play'
|
||||
```
|
||||
|
||||
Some Javascript code :
|
||||
|
||||
```js
|
||||
var config = {
|
||||
duration: 5,
|
||||
comment: 'WTF'
|
||||
}
|
||||
// callbacks beauty un action
|
||||
async_call('/path/to/api', function(json) {
|
||||
another_call(json, function(result2) {
|
||||
another_another_call(result2, function(result3) {
|
||||
another_another_another_call(result3, function(result4) {
|
||||
alert('And if all went well, i got my result :)');
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
The Github Markdown also brings some [nice Emoji support][emoji] : :+1: :heart: :beer:
|
||||
|
||||
[^note-id]: This is the text of the note.
|
||||
|
||||
## About
|
||||
|
||||
This plugin and this sample file is proudly brought to you by the [revolunet team][revolunet]
|
||||
|
||||
[ref1]: http://revolunet.com
|
||||
[ref2]: http://revolunet.com "rich web apps"
|
||||
[MarkdownREF]: http://daringfireball.net/projects/markdown/basics
|
||||
[MarkdownPreview]: https://github.com/revolunet/sublimetext-markdown-preview
|
||||
[ST2]: http://sublimetext.com
|
||||
[revolunet]: http://revolunet.com
|
||||
[revolunet-logo]: http://www.revolunet.com/static/parisjs8/img/logo-revolunet-carre.jpg "revolunet logo"
|
||||
[gfm]: http://github.github.com/github-flavored-markdown/
|
||||
[emoji]: http://www.emoji-cheat-sheet.com/
|
||||
|
||||
|
Reference in New Issue
Block a user