Initial commit. Moved from assembla.com
[g-pypi.git] / g_pypi / cli.py
1 #!/usr/bin/env python
2 # pylint: disable-msg=C0301,W0613,W0612,C0103,E0611,W0511
3
4
5 """
6
7 cli.py
8 ======
9
10 Command-line code for g-pypi
11
12
13 """
14
15 import sys
16 import optparse
17 import inspect
18
19 from pkg_resources import Requirement
20 try:
21     #portage >=2.2
22     from portage import exception as portage_exception
23 except ImportError:
24     #portage <2.2
25     from portage import portage_exception
26
27 from yolk.pypi import CheeseShop
28 from yolk.yolklib import get_highest_version
29 from yolk.setuptools_support import get_download_uri
30 from g_pypi.config import MyConfig
31 from g_pypi.ebuild import Ebuild
32 from g_pypi.portage_utils import ebuild_exists
33 from g_pypi.__init__ import __version__ as VERSION
34
35
36 __docformat__ = 'restructuredtext'
37 __revision__ = '$Revision: 215 $'[11:-1].strip()
38
39
40
41
42 class StdOut:
43
44     """
45     Filter stdout or stderr from specific modules
46     So far this is just used for pkg_resources
47     """
48
49     def __init__(self, stream, modulenames):
50         self.stdout = stream
51         #Modules to squelch
52         self.modulenames = modulenames
53
54     def __getattr__(self, attribute):
55         if not self.__dict__.has_key(attribute) or attribute == '__doc__':
56             return getattr(self.stdout, attribute)
57         return self.__dict__[attribute]
58
59     def write(self, inline):
60         """
61         Write a line to stdout if it isn't in a blacklist
62         
63         Try to get the name of the calling module to see if we want
64         to filter it. If there is no calling module, use current
65         frame in case there's a traceback before there is any calling module
66         """
67         frame = inspect.currentframe().f_back
68         if frame:
69             mod = frame.f_globals.get('__name__') 
70         else:
71             mod = sys._getframe(0).f_globals.get('__name__') 
72         if not mod in self.modulenames:
73             self.stdout.write(inline)
74
75     def writelines(self, inline):
76         """Write multiple lines"""
77         for line in inline:
78             self.write(line)
79
80
81 class GPyPI(object):
82
83     """
84     Main class for command-line interface
85     """
86
87     def __init__(self, package_name, version, options, logger):
88         """
89         @param package_name: case-insensitive package name
90         @type package_name: string
91
92         @param version: package version
93         @type version: string
94
95         @param options: command-line options
96         @type options: OptParser config object
97
98         @param logger: message logger
99         @type logger: logger object
100         """
101
102         self.package_name = package_name
103         self.version = version
104         self. options = options
105         self.logger = logger
106         self.tree = [(package_name, version)]
107         self.pypi = CheeseShop()
108         self.create_ebuilds()
109
110     def raise_error(self, msg):
111         """
112         Cleanup, print error message and raise GPyPiErro
113
114         @param msg: Error message
115         @type msg: string
116
117         """
118         #XXX: Call function to do 'ebuild pkg-ver.ebuild clean' etc.
119         #to clean up unpacked ebuilds
120
121         self.logger.error("Error: " + msg)
122         sys.exit(1)
123
124     def create_ebuilds(self):
125         """
126         Create ebuild for given package_name and any ebuilds for dependencies
127         if needed. If no version is given we use the highest available.
128         """
129         #Create first ebuild then turn off overwrite in case a dependency
130         #ebuild already exists
131         #self.logger.debug("Creating dep tree...")
132         while len(self.tree):
133             (project_name, version) = self.tree.pop(0)
134             #self.logger.debug(self.tree)
135             #self.logger.debug("%s %s" % (project_name, version))
136             self.package_name = project_name
137             self.version = version
138             requires = self.do_ebuild()
139             #print "REQUIRES", requires
140             if requires:
141                 for req in requires:
142                     if self.options.no_deps or ebuild_exists("dev-python/%s" % req.project_name.lower()):
143                         if not self.options.no_deps:
144                             self.logger.info("Skipping dependency (exists): %s" % req.project_name)
145                     else:
146                         self.add_dep(req.project_name)
147             self.options.overwrite = False
148
149     def add_dep(self, project_name):
150         '''Add dependency'''
151         pkgs = []
152         if len(self.tree):
153             for deps in self.tree:
154                 pkgs.append(deps[0])
155
156         if project_name not in pkgs:
157             self.tree.append((project_name, None))
158             #self.logger.info("Dependency needed: %s" % project_name)
159
160     def url_from_pypi(self):
161         """
162         Query PyPI for package's download URL
163         
164         @returns: source URL string
165         """
166
167         try:
168             return self.pypi.get_download_urls(self.package_name, self.version, pkg_type="source")[0]
169         except IndexError:
170             return None
171
172     def find_uri(self, method="setuptools"):
173         """
174         Returns download URI for package
175         If no package version was given it returns highest available
176         Setuptools should find anything xml-rpc can and more.
177
178         @param method: download method can be 'xml-rpc', 'setuptools', or 'all'
179         @type method: string
180
181         @returns download_url string 
182         """
183         download_url = None
184
185         if method == "all" or method == "xml-rpc":
186             download_url = self.url_from_pypi()
187
188         if (method == "all" or method == "setuptools") and not download_url:
189             #Sometimes setuptools can find a package URI if PyPI doesn't have it
190             download_url = self.uri_from_setuptools()
191         return download_url
192
193     def get_uri(self, svn=False):
194         """
195         Attempt to find a package's download URI
196
197         @returns: download_url string
198
199         """
200         download_url = self.find_uri()
201
202         if not download_url:
203             self.raise_error("Can't find SRC_URI for '%s'." %  self.package_name)
204
205         self.logger.debug("Package URI: %s " % download_url)
206         return download_url
207
208     def uri_from_setuptools(self):
209         """
210         Use setuptools to find a package's URI
211         
212         """
213         try:
214             req = Requirement.parse(self.package_name)
215         except ValueError:
216             self.raise_error("The package seems to have a ridiculous name or version, can't proceed.")
217
218         if self.options.subversion:
219             src_uri = get_download_uri(self.package_name, "dev", "source")
220         else:
221             src_uri = get_download_uri(self.package_name, self.version, "source")
222         if not src_uri:
223             self.raise_error("The package has no source URI available.")
224         return src_uri
225
226     def verify_pkgver(self):
227         """
228         Query PyPI to make sure we have correct case for package name
229         """
230
231
232     def do_ebuild(self):
233         """
234         Get SRC_URI using PyPI and attempt to create ebuild
235
236         @returns: tuple with exit code and pkg_resources requirement
237
238         """
239         #Get proper case for project name:
240         (package_name, versions) = self.pypi.query_versions_pypi(self.package_name)
241         if package_name != self.package_name:
242             self.package_name = package_name
243
244
245         if self.version and (self.version not in versions):
246             self.logger.error("Can't find package for version:'%s'." %  self.version)
247             return
248         else:
249             self.version = get_highest_version(versions)
250
251         download_url = self.get_uri()
252         try:
253             ebuild = Ebuild(self.package_name, self.version, download_url)
254         except portage_exception.InvalidVersionString:
255             self.logger.error("Can't determine PV, use -v to set it: %s-%s" % \
256                     (self.package_name, self.version))
257             return
258         except portage_exception.InvalidPackageName:
259             self.logger.error("Can't determine PN, use -n to set it: %s-%s" % \
260                     (self.package_name, self.version))
261             return
262
263         ebuild.set_metadata(self.query_metadata())
264
265         ebuild.get_ebuild()
266         if self.options.pretend:
267             print
268             ebuild.print_ebuild()
269             return
270         return ebuild.create_ebuild()
271
272     def query_metadata(self):
273         """
274         Get package metadata from PyPI
275
276         @returns: metadata text
277
278         """
279
280         if self.version:
281             return self.pypi.release_data(self.package_name, self.version)
282         else:
283             (pn, vers) = self.pypi.query_versions_pypi(self.package_name)
284             return self.pypi.release_data(self.package_name, get_highest_version(vers))
285
286 def parse_pkg_ver(package_spec):
287     """
288     Return tuple with package_name and version from CLI args
289
290     @param package_spec: pkg_resources package spec
291     @type package_spec: string
292
293     @returns: tupe with pkg_name and version
294
295     """
296
297     arg_str = ("").join(package_spec)
298     if "==" not in arg_str:
299         #No version specified
300         package_name = arg_str
301         version = None
302     else:
303         (package_name, version) = arg_str.split("==")
304         package_name = package_name.strip()
305         version = version.strip()
306     return (package_name, version)
307
308 def show_version():
309     """
310     Print g-pypi's version
311     """
312     print "g-pypi version %s (rev. %s)" % (VERSION, __revision__)
313
314 def main():
315     """Parse command-line options and do it."""
316
317     usage = "usage: %prog [options] <package_name[==version]>"
318     opt_parser = optparse.OptionParser(usage=usage)
319
320     opt_parser.add_option("-p", "--pretend", action='store_true', dest=
321                          "pretend", default=False, help=
322                          "Print ebuild to stdout, don't write ebuild file, \
323                          don't download SRC_URI.")
324
325     opt_parser.add_option("-o", "--overwrite", action='store_true', dest=
326                          "overwrite", default=False, help=
327                          "Overwrite existing ebuild.")
328
329     opt_parser.add_option("--no-deps", action='store_true', dest=
330                          "no_deps", default=False, help=
331                          "Don't create ebuilds for any needed dependencies.")
332
333     opt_parser.add_option("-c", "--portage-category", action='store', dest=
334                          "category", default="dev-python", help=
335                          "Specify category to use when creating ebuild. Default is dev-python")
336
337     opt_parser.add_option("-n", "--PN", action='store', dest=
338                          "pn", default=False, help=
339                          "Specify PN to use when naming ebuild.")
340
341     opt_parser.add_option("-v", "--PV", action='store', dest=
342                          "pv", default=False, help=
343                          "Specify PV to use when naming ebuild.")
344
345     opt_parser.add_option("--MY_PV", action='store', dest=
346                          "my_pv", default=False, help=
347                          "Specify MY_PV")
348
349     opt_parser.add_option("--MY_PN", action='store', dest=
350                          "my_pn", default=False, help=
351                          "Specify MY_PN")
352
353     opt_parser.add_option("--MY_P", action='store', dest=
354                          "my_p", default=False, help=
355                          "Specify MY_P")
356
357     opt_parser.add_option("--format", action='store', dest=
358                          "format", default=None, help=
359                          "Format when printing to stdout: ansi, html, bbcode, or none")
360     opt_parser.add_option("-s", "--subversion", action='store_true', dest=
361                          "subversion", default=False, help=
362                          "Create live subversion ebuild if repo is available.")
363
364     opt_parser.add_option("-V", "--verbose", action='store_true', dest=
365                          "verbose", default=False, help=
366                          "Show more output.")
367     opt_parser.add_option("-q", "--quiet", action='store_true', dest=
368                          "quiet", default=False, help=
369                          "Show less output.")
370
371     opt_parser.add_option("-d", "--debug", action='store_true', dest=
372                          "debug", default=False, help=
373                          "Show debug information.")
374
375
376     opt_parser.add_option("--version", action='store_true', dest=
377                          "version", default=False, help=
378                          "Show g-pypi version and exit.")
379
380     (options, package_spec) = opt_parser.parse_args()
381     if options.version:
382         show_version()
383         return
384
385     #Turn off all output from the pkg_resources module by default
386     sys.stdout = StdOut(sys.stdout, ['distutils.log'])
387     sys.stderr = StdOut(sys.stderr, ['distutils.log'])
388
389     config = MyConfig()
390     config.set_options(options)
391     config.set_logger()
392     logger = config.get_logger()
393     
394     if not package_spec:
395         opt_parser.print_help()
396         logger.error("\nError: You need to specify a package name at least.")
397         return 1
398     (package_name, version) = parse_pkg_ver(package_spec)
399     gpypi = GPyPI(package_name, version, options, logger)
400
401 if __name__ == "__main__":
402     sys.exit(main())
403