setup.py 12 KB

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