setup.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. #!/usr/bin/env python
  2. """
  3. script to install pwman3
  4. """
  5. import datetime
  6. from distutils.core import Command
  7. from distutils.errors import DistutilsOptionError
  8. import argparse
  9. from setuptools import setup
  10. from setuptools import find_packages
  11. import sys
  12. import os
  13. # The BuildManPage code is distributed
  14. # under the same License of Python
  15. # Copyright (c) 2014 Oz Nahum Tiram <nahumoz@gmail.com>
  16. """
  17. Add a `build_manpage` command to your setup.py.
  18. To use this Command class import the class to your setup.py,
  19. and add a command to call this class::
  20. from build_manpage import BuildManPage
  21. ...
  22. ...
  23. setup(
  24. ...
  25. ...
  26. cmdclass={
  27. 'build_manpage': BuildManPage,
  28. )
  29. You can then use the following setup command to produce a man page::
  30. $ python setup.py build_manpage --output=prog.1
  31. --parser=yourmodule:argparser
  32. Alternatively, set the variable AUTO_BUILD to True, and just invoke::
  33. $ python setup.py build
  34. If automatically want to build the man page every time you invoke your build,
  35. add to your ```setup.cfg``` the following::
  36. [build_manpage]
  37. output = <appname>.1
  38. parser = <path_to_your_parser>
  39. """
  40. # build.sub_commands.append(('build_manpage', None))
  41. class TestCommand(Command):
  42. user_options = []
  43. def initialize_options(self):
  44. pass
  45. def finalize_options(self):
  46. pass
  47. def run(self):
  48. import sys
  49. import subprocess
  50. raise SystemExit(
  51. subprocess.call([sys.executable,
  52. '-m',
  53. 'tests.test_pwman']))
  54. class IntegrationTestCommand(Command):
  55. user_options = []
  56. def initialize_options(self):
  57. pass
  58. def finalize_options(self):
  59. pass
  60. def run(self):
  61. import sys
  62. import subprocess
  63. raise SystemExit(
  64. subprocess.call([sys.executable,
  65. '-m',
  66. 'tests.test_integration']))
  67. class BuildManPage(Command):
  68. description = 'Generate man page from an ArgumentParser instance.'
  69. user_options = [
  70. ('output=', 'O', 'output file'),
  71. ('parser=', None, 'module path to an ArgumentParser instance'
  72. '(e.g. mymod:func, where func is a method or function which return'
  73. 'an arparse.ArgumentParser instance.'),
  74. ]
  75. def initialize_options(self):
  76. self.output = None
  77. self.parser = None
  78. def finalize_options(self):
  79. if self.output is None:
  80. raise DistutilsOptionError('\'output\' option is required')
  81. if self.parser is None:
  82. raise DistutilsOptionError('\'parser\' option is required')
  83. mod_name, func_name = self.parser.split(':')
  84. fromlist = mod_name.split('.')
  85. try:
  86. mod = __import__(mod_name, fromlist=fromlist)
  87. self._parser = getattr(mod, func_name)(
  88. formatter_class=ManPageFormatter)
  89. except ImportError as err:
  90. raise err
  91. self.announce('Writing man page %s' % self.output)
  92. self._today = datetime.date.today()
  93. def run(self):
  94. dist = self.distribution
  95. homepage = dist.get_url()
  96. appname = self._parser.prog
  97. sections = {'authors': ("pwman3 was originally written by Ivan Kelly "
  98. "<ivan@ivankelly.net>.\n pwman3 is now "
  99. "maintained "
  100. "by Oz Nahum <nahumoz@gmail.com>."),
  101. 'distribution': ("The latest version of {} may be "
  102. "downloaded from {}".format(appname,
  103. homepage))
  104. }
  105. dist = self.distribution
  106. mpf = ManPageFormatter(appname,
  107. desc=dist.get_description(),
  108. long_desc=dist.get_long_description(),
  109. ext_sections=sections)
  110. m = mpf.format_man_page(self._parser)
  111. with open(self.output, 'w') as f:
  112. f.write(m)
  113. class ManPageFormatter(argparse.HelpFormatter):
  114. """
  115. Formatter class to create man pages.
  116. This class relies only on the parser, and not distutils.
  117. The following shows a scenario for usage::
  118. from pwman import parser_options
  119. from build_manpage import ManPageFormatter
  120. # example usage ...
  121. dist = distribution
  122. mpf = ManPageFormatter(appname,
  123. desc=dist.get_description(),
  124. long_desc=dist.get_long_description(),
  125. ext_sections=sections)
  126. # parser is an ArgumentParser instance
  127. m = mpf.format_man_page(parsr)
  128. with open(self.output, 'w') as f:
  129. f.write(m)
  130. The last line would print all the options and help infomation wrapped with
  131. man page macros where needed.
  132. """
  133. def __init__(self,
  134. prog,
  135. indent_increment=2,
  136. max_help_position=24,
  137. width=None,
  138. section=1,
  139. desc=None,
  140. long_desc=None,
  141. ext_sections=None,
  142. authors=None,
  143. ):
  144. super(ManPageFormatter, self).__init__(prog)
  145. self._prog = prog
  146. self._section = 1
  147. self._today = datetime.date.today().strftime('%Y\\-%m\\-%d')
  148. self._desc = desc
  149. self._long_desc = long_desc
  150. self._ext_sections = ext_sections
  151. def _get_formatter(self, **kwargs):
  152. return self.formatter_class(prog=self.prog, **kwargs)
  153. def _markup(self, txt):
  154. return txt.replace('-', '\\-')
  155. def _underline(self, string):
  156. return "\\fI\\s-1" + string + "\\s0\\fR"
  157. def _bold(self, string):
  158. if not string.strip().startswith('\\fB'):
  159. string = '\\fB' + string
  160. if not string.strip().endswith('\\fR'):
  161. string = string + '\\fR'
  162. return string
  163. def _mk_synopsis(self, parser):
  164. self.add_usage(parser.usage, parser._actions,
  165. parser._mutually_exclusive_groups, prefix='')
  166. usage = self._format_usage(None, parser._actions,
  167. parser._mutually_exclusive_groups, '')
  168. usage = usage.replace('%s ' % self._prog, '')
  169. usage = '.SH SYNOPSIS\n \\fB%s\\fR %s\n' % (self._markup(self._prog),
  170. usage)
  171. return usage
  172. def _mk_title(self, prog):
  173. return '.TH {0} {1} {2}\n'.format(prog, self._section,
  174. self._today)
  175. def _make_name(self, parser):
  176. """
  177. this method is in consitent with others ... it relies on
  178. distribution
  179. """
  180. return '.SH NAME\n%s \\- %s\n' % (parser.prog,
  181. parser.description)
  182. def _mk_description(self):
  183. if self._long_desc:
  184. long_desc = self._long_desc.replace('\n', '\n.br\n')
  185. return '.SH DESCRIPTION\n%s\n' % self._markup(long_desc)
  186. else:
  187. return ''
  188. def _mk_footer(self, sections):
  189. if not hasattr(sections, '__iter__'):
  190. return ''
  191. footer = []
  192. for section, value in sections.items():
  193. part = ".SH {}\n {}".format(section.upper(), value)
  194. footer.append(part)
  195. return '\n'.join(footer)
  196. def format_man_page(self, parser):
  197. page = []
  198. page.append(self._mk_title(self._prog))
  199. page.append(self._mk_synopsis(parser))
  200. page.append(self._mk_description())
  201. page.append(self._mk_options(parser))
  202. page.append(self._mk_footer(self._ext_sections))
  203. return ''.join(page)
  204. def _mk_options(self, parser):
  205. formatter = parser._get_formatter()
  206. # positionals, optionals and user-defined groups
  207. for action_group in parser._action_groups:
  208. formatter.start_section(None)
  209. formatter.add_text(None)
  210. formatter.add_arguments(action_group._group_actions)
  211. formatter.end_section()
  212. # epilog
  213. formatter.add_text(parser.epilog)
  214. # determine help from format above
  215. return '.SH OPTIONS\n' + formatter.format_help()
  216. def _format_action_invocation(self, action):
  217. if not action.option_strings:
  218. metavar, = self._metavar_formatter(action, action.dest)(1)
  219. return metavar
  220. else:
  221. parts = []
  222. # if the Optional doesn't take a value, format is:
  223. # -s, --long
  224. if action.nargs == 0:
  225. parts.extend([self._bold(action_str) for action_str in
  226. action.option_strings])
  227. # if the Optional takes a value, format is:
  228. # -s ARGS, --long ARGS
  229. else:
  230. default = self._underline(action.dest.upper())
  231. args_string = self._format_args(action, default)
  232. for option_string in action.option_strings:
  233. parts.append('%s %s' % (self._bold(option_string),
  234. args_string))
  235. return ', '.join(parts)
  236. class ManPageCreator(object):
  237. """
  238. This class takes a little different approach. Instead of relying on
  239. information from ArgumentParser, it relies on information retrieved
  240. from distutils.
  241. This class makes it easy for package maintainer to create man pages in
  242. cases, that there is no ArgumentParser.
  243. """
  244. def _mk_name(self, distribution):
  245. """
  246. """
  247. return '.SH NAME\n%s \\- %s\n' % (distribution.get_name(),
  248. distribution.get_description())
  249. sys.path.insert(0, os.getcwd())
  250. install_requires = ['colorama>=0.2.4', 'cryptography']
  251. if sys.platform.startswith('win'):
  252. install_requires.append('pyreadline')
  253. long_description = u"""\
  254. Pwman3 aims to provide a simple but powerful commandline interface for
  255. password management.
  256. It allows one to store passwords in database locked by master password which
  257. is AES encrypted.
  258. Pwman3 supports MySQL, Postgresql and SQLite and even MongoDB"""
  259. packages = find_packages(exclude=['tests', 'pwman/ui/templates'])
  260. setup(name='pwman3',
  261. version='0.9.11',
  262. description=("a command line password manager with support for multiple"
  263. " databases."),
  264. long_description=long_description,
  265. author='Oz Nahum Tiram',
  266. author_email='nahumoz@gmail.com',
  267. url='http://pwman3.github.io/pwman3/',
  268. license="GNU GPL",
  269. packages=packages,
  270. include_package_data=True,
  271. zip_safe=False,
  272. install_requires=install_requires,
  273. keywords="password-manager crypto cli",
  274. classifiers=['Environment :: Console',
  275. 'Intended Audience :: End Users/Desktop',
  276. 'Intended Audience :: Developers',
  277. 'Intended Audience :: System Administrators',
  278. ('License :: OSI Approved :: GNU General Public License'
  279. ' v3 or later (GPLv3+)'),
  280. 'Operating System :: OS Independent',
  281. 'Programming Language :: Python',
  282. 'Programming Language :: Python :: 3',
  283. 'Programming Language :: Python :: 3.2',
  284. 'Programming Language :: Python :: 3.3',
  285. 'Programming Language :: Python :: 3.4',
  286. 'Programming Language :: Python :: 3.5',
  287. ],
  288. cmdclass={
  289. 'build_manpage': BuildManPage,
  290. 'test': TestCommand,
  291. 'integration': IntegrationTestCommand
  292. },
  293. entry_points={
  294. 'console_scripts': ['pwman3 = pwman.ui.cli:main']
  295. }
  296. )