Merge lp:~sil/quickly/add-flash-game-template into lp:quickly

Proposed by Stuart Langridge
Status: Merged
Merged at revision: 581
Proposed branch: lp:~sil/quickly/add-flash-game-template
Merge into: lp:quickly
Diff against target: 1653 lines (+1578/-0)
14 files modified
data/templates/flash-game/commandsconfig (+10/-0)
data/templates/flash-game/create.py (+158/-0)
data/templates/flash-game/internal/SWF.py (+89/-0)
data/templates/flash-game/internal/apportutils.py (+143/-0)
data/templates/flash-game/internal/bzrutils.py (+37/-0)
data/templates/flash-game/internal/launchpad_helper.py (+92/-0)
data/templates/flash-game/internal/packaging.py (+407/-0)
data/templates/flash-game/internal/quicklyutils.py (+411/-0)
data/templates/flash-game/package.py (+73/-0)
data/templates/flash-game/project_root/AUTHORS (+1/-0)
data/templates/flash-game/project_root/bin/project_name (+71/-0)
data/templates/flash-game/project_root/data/index.html (+9/-0)
data/templates/flash-game/project_root/project_name.desktop.in (+8/-0)
data/templates/flash-game/project_root/setup.py (+69/-0)
To merge this branch: bzr merge lp:~sil/quickly/add-flash-game-template
Reviewer Review Type Date Requested Status
Didier Roche-Tolomelli Approve
Review via email: mp+45059@code.launchpad.net

Description of the change

Allow creation of a Quickly project to wrap an existing SWF Flash file into a working Ubuntu application, by displaying it in a Gtk window, and allowing Quickly's easy-to-do packaging and release and so on.

To post a comment you must log in.
Revision history for this message
Didier Roche-Tolomelli (didrocks) wrote :

All is good as discussed on IRC and made some review! That's an awesome contribution, I'm sure we will see a lot of people making Flash game available on ubuntu thanks to that template!

Thanks Stuart :)
Approved for now, will merge tomorrow with a better network connection

review: Approve
584. By Stuart Langridge

Add a .desktop file for the packaged game

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'data/templates/flash-game'
2=== added file 'data/templates/flash-game/commandsconfig'
3--- data/templates/flash-game/commandsconfig 1970-01-01 00:00:00 +0000
4+++ data/templates/flash-game/commandsconfig 2011-01-03 19:29:40 +0000
5@@ -0,0 +1,10 @@
6+# define parameters for commands, putting them in a list seperated
7+# by ';'
8+# if nothing specified, default is to launch command inside a project
9+# only and not be followed by a template
10+#COMMANDS_LAUNCHED_IN_OR_OUTSIDE_PROJECT =
11+COMMANDS_LAUNCHED_OUTSIDE_PROJECT_ONLY = create
12+#COMMANDS_FOLLOWED_BY_COMMAND =
13+
14+[ubuntu-application]
15+IMPORT=configure;create;debug;edit;license;package;release;run;save;share
16
17=== added file 'data/templates/flash-game/create.py'
18--- data/templates/flash-game/create.py 1970-01-01 00:00:00 +0000
19+++ data/templates/flash-game/create.py 2011-01-03 19:29:40 +0000
20@@ -0,0 +1,158 @@
21+#!/usr/bin/python
22+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
23+# Copyright 2009 Didier Roche
24+#
25+# This file is part of Quickly ubuntu-application template
26+#
27+#This program is free software: you can redistribute it and/or modify it
28+#under the terms of the GNU General Public License version 3, as published
29+#by the Free Software Foundation.
30+
31+#This program is distributed in the hope that it will be useful, but
32+#WITHOUT ANY WARRANTY; without even the implied warranties of
33+#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
34+#PURPOSE. See the GNU General Public License for more details.
35+
36+#You should have received a copy of the GNU General Public License along
37+#with this program. If not, see <http://www.gnu.org/licenses/>.
38+
39+import sys
40+import os
41+import shutil
42+import subprocess
43+
44+from quickly import templatetools
45+from internal import quicklyutils, SWF
46+
47+import gettext
48+from gettext import gettext as _
49+# set domain text
50+gettext.textdomain('quickly')
51+
52+
53+
54+def help():
55+ print _("""Usage:
56+$ quickly create flash-game path/to/project_name path/to/myflashgame.swf
57+
58+where "project_name" is one or more words separated by an underscore and
59+path/to can be any existing path.
60+
61+This will create a new project which runs your existing SWF, myflashgame.swf,
62+on the Ubuntu desktop, and makes it ready to be packaged and distributed in
63+the Ubuntu Software Centre.
64+
65+After creating the project, you'll want to specify the title of your game
66+and the size of the window:
67+
68+1. Changing your working directory to the new project:
69+$ cd path/to/project_name
70+
71+3. Edit the code and specify the title and window size:
72+$ quickly edit
73+""")
74+templatetools.handle_additional_parameters(sys.argv, help)
75+
76+
77+# get the name of the project
78+if len(sys.argv) < 2:
79+ print _("""Project name not defined.\nUsage is: quickly create flash-game project_name myflashgame.swf""")
80+ sys.exit(4)
81+
82+if len(sys.argv) < 3:
83+ print _("""Flash SWF file not defined.\nUsage is: quickly create flash-game project_name myflashgame.swf""")
84+ sys.exit(5)
85+
86+path_and_project = sys.argv[1].split('/')
87+project_name = path_and_project[-1]
88+
89+swf = os.path.realpath(sys.argv[2])
90+if not os.path.exists(swf):
91+ print _("""Flash SWF file '%s' not found.\nUsage is: quickly create flash-game project_name myflashgame.swf""" % swf)
92+ sys.exit(6)
93+
94+# check that project name follow quickly rules and reformat it.
95+try:
96+ project_name = templatetools.quickly_name(project_name)
97+except templatetools.bad_project_name, e:
98+ print(e)
99+ sys.exit(1)
100+
101+os.chdir(project_name)
102+
103+# get origin path
104+pathname = templatetools.get_template_path_from_project()
105+abs_path_project_root = os.path.join(pathname, 'project_root')
106+
107+python_name = templatetools.python_name(project_name)
108+sentence_name, camel_case_name = quicklyutils.conventional_names(project_name)
109+
110+# Calculate the SWF's dimensions
111+try:
112+ width, height = SWF.dimensions(swf)
113+except SWF.SWFNotASWF:
114+ print "File '%s' does not seem to be a SWF. Terminating."
115+ sys.exit(7)
116+except SWF.SWFNoDimensions:
117+ print "(Could not read size from SWF file; defaulting to 640x480. You should edit bin/%s with the correct size.)" % project_name
118+ width, height = (640, 480)
119+
120+
121+substitutions = (("project_name",project_name),
122+ ("camel_case_name",camel_case_name),
123+ ("python_name",python_name),
124+ ("sentence_name",sentence_name),
125+ ("swf_height",str(height)),
126+ ("swf_width",str(width)),
127+ )
128+
129+
130+for root, dirs, files in os.walk(abs_path_project_root):
131+ try:
132+ relative_dir = root.split('project_root/')[1]
133+ except:
134+ relative_dir = ""
135+ # python dir should be replace by python_name (project "pythonified" name)
136+ if relative_dir.startswith('python'):
137+ relative_dir = relative_dir.replace('python', python_name)
138+
139+ for directory in dirs:
140+ if directory == 'python':
141+ directory = python_name
142+ os.mkdir(os.path.join(relative_dir, directory))
143+ for filename in files:
144+ quicklyutils.file_from_template(root, filename, relative_dir, substitutions)
145+
146+# set the mode to executable for executable file
147+exec_file = os.path.join('bin', project_name)
148+try:
149+ os.chmod(exec_file, 0755)
150+except:
151+ pass
152+
153+# Copy the specified SWF file into the project
154+shutil.copyfile(swf, os.path.join("data", "game.swf"))
155+
156+# We require a specific version of the ubuntu-application template, so
157+# edit the project's .quickly file to specify it.
158+#WORKAROUND
159+fp = open(".quickly", "a")
160+fp.write("\nversion_ubuntu-application = 0.4\n")
161+fp.close()
162+
163+# add it to revision control
164+print _("Creating bzr repository and commiting")
165+bzr_instance = subprocess.Popen(["bzr", "init"], stdout=subprocess.PIPE)
166+bzr_instance.wait()
167+bzr_instance = subprocess.Popen(["bzr", "add"], stdout=subprocess.PIPE)
168+bzr_instance.wait()
169+subprocess.Popen(["bzr", "commit", "-m", "Initial project creation with Quickly!"], stderr=subprocess.PIPE)
170+
171+# run the new application if X display
172+if templatetools.is_X_display() and os.path.isfile(exec_file):
173+ print _("Launching your newly created project!")
174+ subprocess.call(['./' + project_name], cwd='bin/')
175+
176+print _("Congratulations, your new project is set up! cd %s/ to edit the details.") % os.getcwd()
177+
178+sys.exit(0)
179
180=== added directory 'data/templates/flash-game/internal'
181=== added file 'data/templates/flash-game/internal/SWF.py'
182--- data/templates/flash-game/internal/SWF.py 1970-01-01 00:00:00 +0000
183+++ data/templates/flash-game/internal/SWF.py 2011-01-03 19:29:40 +0000
184@@ -0,0 +1,89 @@
185+import struct, zlib
186+
187+class SWFNotASWF(Exception): pass
188+class SWFNoDimensions(Exception): pass
189+
190+def parse(input):
191+ """Parses the header information from an SWF file.
192+ Code from http://pypi.python.org/pypi/hexagonit.swfheader, GPL licenced."""
193+ if hasattr(input, 'read'):
194+ input.seek(0)
195+ else:
196+ input = open(input, 'rb')
197+
198+ def read_ui8(c):
199+ return struct.unpack('<B', c)[0]
200+ def read_ui16(c):
201+ return struct.unpack('<H', c)[0]
202+ def read_ui32(c):
203+ return struct.unpack('<I', c)[0]
204+
205+ header = {}
206+
207+ # Read the 3-byte signature field
208+ signature = ''.join(struct.unpack('<3c', input.read(3)))
209+ if signature not in ('FWS', 'CWS'):
210+ raise ValueError('Invalid SWF signature: %s' % signature)
211+
212+ # Compression
213+ header['compressed'] = signature.startswith('C')
214+
215+ # Version
216+ header['version'] = read_ui8(input.read(1))
217+
218+ # File size (stored as a 32-bit integer)
219+ header['size'] = read_ui32(input.read(4))
220+
221+ # Payload
222+ buffer = input.read(header['size'])
223+ if header['compressed']:
224+ # Unpack the zlib compression
225+ buffer = zlib.decompress(buffer)
226+
227+ # Containing rectangle (struct RECT)
228+
229+ # The number of bits used to store the each of the RECT values are
230+ # stored in first five bits of the first byte.
231+ nbits = read_ui8(buffer[0]) >> 3
232+
233+ current_byte, buffer = read_ui8(buffer[0]), buffer[1:]
234+ bit_cursor = 5
235+
236+ for item in 'xmin', 'xmax', 'ymin', 'ymax':
237+ value = 0
238+ for value_bit in range(nbits-1, -1, -1): # == reversed(range(nbits))
239+ if (current_byte << bit_cursor) & 0x80:
240+ value |= 1 << value_bit
241+ # Advance the bit cursor to the next bit
242+ bit_cursor += 1
243+
244+ if bit_cursor > 7:
245+ # We've exhausted the current byte, consume the next one
246+ # from the buffer.
247+ current_byte, buffer = read_ui8(buffer[0]), buffer[1:]
248+ bit_cursor = 0
249+
250+ # Convert value from TWIPS to a pixel value
251+ header[item] = value / 20
252+
253+ header['width'] = header['xmax'] - header['xmin']
254+ header['height'] = header['ymax'] - header['ymin']
255+
256+ header['frames'] = read_ui16(buffer[0:2])
257+ header['fps'] = read_ui16(buffer[2:4])
258+
259+ input.close()
260+ return header
261+
262+
263+def dimensions(swf):
264+ """Read the dimensions of a SWF, as per the Adobe spec.
265+ Spec downloaded from http://www.adobe.com/devnet/swf.html."""
266+ try:
267+ details = parse(swf)
268+ except:
269+ raise SWFNotASWF
270+ try:
271+ return (details["width"], details["height"])
272+ except:
273+ raise SWFNoDimensions
274
275=== added file 'data/templates/flash-game/internal/__init__.py'
276=== added file 'data/templates/flash-game/internal/apportutils.py'
277--- data/templates/flash-game/internal/apportutils.py 1970-01-01 00:00:00 +0000
278+++ data/templates/flash-game/internal/apportutils.py 2011-01-03 19:29:40 +0000
279@@ -0,0 +1,143 @@
280+import os
281+import shutil
282+import subprocess
283+
284+from gettext import gettext as _
285+
286+import quickly
287+import quicklyutils
288+
289+from lxml import etree
290+
291+LPI_import_block = """
292+# optional Launchpad integration
293+# this shouldn't crash if not found as it is simply used for bug reporting
294+try:
295+ import LaunchpadIntegration
296+ launchpad_available = True
297+except:
298+ launchpad_available = False
299+
300+"""
301+
302+LPI_init_menu_block = """
303+ global launchpad_available
304+ if launchpad_available:
305+ # see https://wiki.ubuntu.com/UbuntuDevelopment/Internationalisation/Coding for more information
306+ # about LaunchpadIntegration
307+ helpmenu = self.builder.get_object('%(help_menu)s')
308+ if helpmenu:
309+ LaunchpadIntegration.set_sourcepackagename('%(project_name)s')
310+ LaunchpadIntegration.add_items(helpmenu, 0, False, True)
311+ else:
312+ launchpad_available = False"""
313+
314+def update_apport(project_name, old_lp_project, new_lp_project):
315+ if not new_lp_project:
316+ return
317+ # crashdb file doesn't support spaces or dashes in the crash db name
318+ safe_project_name = project_name.replace(" ", "_").replace("-","_")
319+ crashdb_file = "%s-crashdb.conf"%project_name
320+ hook_file = "source_%s.py"%project_name
321+
322+
323+ pathname = quickly.templatetools.get_template_path_from_project()
324+ template_pr_path = os.path.join(os.path.abspath(pathname), "store",
325+ "apport")
326+ relative_crashdb_dir = os.path.join("etc", "apport", "crashdb.conf.d")
327+ relative_apport_dir = "apport"
328+
329+ existing_crashdb = os.path.join(relative_crashdb_dir, crashdb_file)
330+ existing_hook = os.path.join(relative_apport_dir, hook_file)
331+
332+ template_crashdb_dir = os.path.join(template_pr_path, relative_crashdb_dir)
333+ template_hook_dir = os.path.join(template_pr_path, relative_apport_dir)
334+
335+ # if the project name has changed, or any of the files are missing, then
336+ # attempt to set up the apport configuration and hooks
337+ if not old_lp_project == new_lp_project \
338+ or not os.path.isfile(existing_crashdb) \
339+ or not os.path.isfile(existing_hook):
340+
341+ subst_existing = ((old_lp_project, new_lp_project),)
342+ subst_new = ( ("safe_project_name", safe_project_name),
343+ ("project_name", project_name),
344+ ("lp_project", new_lp_project))
345+
346+ if os.path.isfile(existing_crashdb):
347+ print _("Updating project name references in existing apport crashdb configuration")
348+ quicklyutils.file_from_template(relative_crashdb_dir, crashdb_file, relative_crashdb_dir, subst_existing)
349+ elif os.path.isdir(template_crashdb_dir):
350+ print _("Creating new apport crashdb configuration")
351+ if not os.path.isdir(relative_crashdb_dir):
352+ os.makedirs(relative_crashdb_dir)
353+ quicklyutils.file_from_template(template_crashdb_dir, "project_name-crashdb.conf", relative_crashdb_dir, subst_new)
354+
355+ if not os.path.isfile(existing_hook) and os.path.isdir(template_hook_dir):
356+ print _("Creating new apport hooks")
357+ if not os.path.isdir(relative_apport_dir):
358+ os.makedirs(relative_apport_dir)
359+ quicklyutils.file_from_template(template_hook_dir, "source_project_name.py", relative_apport_dir, subst_new)
360+
361+def insert_lpi_if_required(project_name):
362+ existing_bin_filename = os.path.join("bin",project_name)
363+ camel_case_project_name = quickly.templatetools.get_camel_case_name(project_name)
364+ existing_ui_filename = os.path.join("data","ui", "%sWindow.ui"%camel_case_project_name)
365+
366+ if os.path.isfile(existing_bin_filename) and os.path.isfile(existing_ui_filename):
367+ tree = etree.parse(existing_ui_filename)
368+ help_menu = find_about_menu(tree)
369+
370+ if help_menu:
371+ existing_bin_file = file(existing_bin_filename, "r")
372+ existing_lines = existing_bin_file.readlines()
373+ existing_bin_file.close()
374+ new_lines = detect_or_insert_lpi(existing_lines, project_name, help_menu)
375+ if new_lines:
376+ print _("Adding launchpad integration to existing application")
377+ ftarget_file_name_out = file(existing_bin_file.name + '.new', 'w')
378+ ftarget_file_name_out.writelines(new_lines)
379+ ftarget_file_name_out.close()
380+ quickly.templatetools.apply_file_rights(existing_bin_file.name, ftarget_file_name_out.name)
381+ os.rename(ftarget_file_name_out.name, existing_bin_file.name)
382+ return True
383+ return False
384+
385+def detect_or_insert_lpi(existing_lines, project_name, help_menu):
386+ integration_present = False
387+ import_insert_line = None
388+ init_insert_line = None
389+ current_line = 0
390+ for line in existing_lines:
391+ if "import LaunchpadIntegration" in line \
392+ or "if launchpad_available:" in line:
393+ integration_present = True
394+ break
395+ if not import_insert_line and "import gtk" in line:
396+ import_insert_line = current_line
397+ if not init_insert_line and "self.builder.connect_signals(self)" in line:
398+ init_insert_line = current_line
399+ current_line += 1
400+
401+ if not integration_present \
402+ and import_insert_line \
403+ and init_insert_line \
404+ and import_insert_line < init_insert_line:
405+ init_menu_block = LPI_init_menu_block%{"project_name":project_name, "help_menu":help_menu}
406+ existing_lines = existing_lines[:import_insert_line+1] + \
407+ ["%s\n"%l for l in LPI_import_block.splitlines()] + \
408+ existing_lines[import_insert_line+1:init_insert_line+1] + \
409+ ["%s\n"%l for l in init_menu_block.splitlines()] + \
410+ existing_lines[init_insert_line+1:]
411+ return existing_lines
412+ else:
413+ return None
414+
415+
416+def find_about_menu(tree):
417+ """Finds the current help menu in the passed xml document by looking for the gtk-about element"""
418+ help_item = tree.xpath('//property[@name="label" and .="gtk-about"]/../../../@id')
419+ if len(help_item) == 1: # only one element matching this should be found
420+ return help_item[0]
421+ else:
422+ return None
423
424=== added file 'data/templates/flash-game/internal/bzrutils.py'
425--- data/templates/flash-game/internal/bzrutils.py 1970-01-01 00:00:00 +0000
426+++ data/templates/flash-game/internal/bzrutils.py 2011-01-03 19:29:40 +0000
427@@ -0,0 +1,37 @@
428+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
429+# Copyright 2010 Didier Roche
430+#
431+# This file is part of Quickly ubuntu-application template
432+#
433+#This program is free software: you can redistribute it and/or modify it
434+#under the terms of the GNU General Public License version 3, as published
435+#by the Free Software Foundation.
436+
437+#This program is distributed in the hope that it will be useful, but
438+#WITHOUT ANY WARRANTY; without even the implied warranties of
439+#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
440+#PURPOSE. See the GNU General Public License for more details.
441+
442+#You should have received a copy of the GNU General Public License along
443+#with this program. If not, see <http://www.gnu.org/licenses/>.
444+
445+from quickly import configurationhandler
446+
447+def set_bzrbranch(bzr_branch):
448+ '''set default bzr branch from where to pull and push'''
449+
450+ if not configurationhandler.project_config:
451+ configurationhandler.loadConfig()
452+ configurationhandler.project_config['bzrbranch'] = bzr_branch
453+
454+def get_bzrbranch():
455+ '''get default bzr branch from where to pull and push'''
456+
457+ if not configurationhandler.project_config:
458+ configurationhandler.loadConfig()
459+
460+ try:
461+ bzr_branch = configurationhandler.project_config['bzrbranch']
462+ except KeyError:
463+ bzr_branch = None
464+ return bzr_branch
465
466=== added file 'data/templates/flash-game/internal/launchpad_helper.py'
467--- data/templates/flash-game/internal/launchpad_helper.py 1970-01-01 00:00:00 +0000
468+++ data/templates/flash-game/internal/launchpad_helper.py 2011-01-03 19:29:40 +0000
469@@ -0,0 +1,92 @@
470+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
471+# Copyright 2010 Didier Roche, some part based on
472+# Martin Pitt <martin.pitt@ubuntu.com>
473+# and http://blog.launchpad.net/api/recipe-for-uploading-files-via-the-api
474+#
475+# This file is part of Quickly ubuntu-application template
476+#
477+#This program is free software: you can redistribute it and/or modify it
478+#under the terms of the GNU General Public License version 3, as published
479+#by the Free Software Foundation.
480+
481+#This program is distributed in the hope that it will be useful, but
482+#WITHOUT ANY WARRANTY; without even the implied warranties of
483+#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
484+#PURPOSE. See the GNU General Public License for more details.
485+
486+#You should have received a copy of the GNU General Public License along
487+#with this program. If not, see <http://www.gnu.org/licenses/>.
488+
489+import datetime
490+import os
491+import sys
492+import subprocess
493+
494+import gettext
495+from gettext import gettext as _
496+gettext.textdomain('quickly')
497+
498+#TODO: see if 0 release in the project
499+
500+def create_release(project, version):
501+ '''Create new release and milestone for LP project.
502+
503+ If more than one release already exists, take the last one.'''
504+
505+ release_date = datetime.date.today().strftime('%Y-%m-%d')
506+ if len(project.series) == 0:
507+ print "No serie is not supported right now. Not uploading the tarball for you"
508+ sys.exit(1)
509+ serie = project.series[project.series.total_size - 1]
510+ milestone = serie.newMilestone(name=version,
511+ date_targeted=release_date)
512+ return milestone.createProductRelease(date_released=release_date)
513+
514+def push_tarball_to_launchpad(project, version, tarball, changelog_content):
515+ '''Push new tarball to Launchpad, create release if needed and sign it'''
516+
517+ # Find the release in the project's releases collection.
518+ release = None
519+ for rel in project.releases:
520+ if rel.version == version:
521+ release = rel
522+ break
523+ if not release:
524+ release = create_release(project, version)
525+
526+ # Get the file contents.
527+ file_content = open(tarball, 'r').read()
528+ # Get the signature, if available.
529+ signature = tarball + '.asc'
530+ if not os.path.exists(signature):
531+ print _('Calling GPG to create tarball signature...')
532+ if subprocess.call(['gpg', '--armor', '--sign', '--detach-sig',
533+ tarball]) != 0:
534+ sys.stderr.write(_('Signing the tarball failed, not uploading the ' \
535+ 'signature'))
536+
537+ if os.path.exists(signature):
538+ signature_content = open(signature, 'r').read()
539+ else:
540+ signature_content = None
541+
542+ # Create a new product release file.
543+ tarball_pretty_name = os.path.basename(tarball)
544+ signature_pretty_name = os.path.basename(signature)
545+ release.add_file(filename=tarball_pretty_name, description='%s tarball' % version,
546+ file_content=file_content, content_type='appplication/x-gzip',
547+ file_type='Code Release Tarball', signature_filename=signature_pretty_name,
548+ signature_content=signature_content)
549+
550+ if not changelog_content:
551+ changelog_content = _('New release available: %s') % version
552+ else:
553+ changelog_content = "\n".join(changelog_content)
554+ release.changelog = changelog_content
555+ release.release_notes = changelog_content
556+ try:
557+ release.lp_save()
558+ except HTTPError, e:
559+ print(_('An error happened during tarball upload:'), e.content)
560+ sys.exit(1)
561+
562
563=== added file 'data/templates/flash-game/internal/packaging.py'
564--- data/templates/flash-game/internal/packaging.py 1970-01-01 00:00:00 +0000
565+++ data/templates/flash-game/internal/packaging.py 2011-01-03 19:29:40 +0000
566@@ -0,0 +1,407 @@
567+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
568+# Copyright 2009 Didier Roche
569+#
570+# This file is part of Quickly ubuntu-application template
571+#
572+#This program is free software: you can redistribute it and/or modify it
573+#under the terms of the GNU General Public License version 3, as published
574+#by the Free Software Foundation.
575+
576+#This program is distributed in the hope that it will be useful, but
577+#WITHOUT ANY WARRANTY; without even the implied warranties of
578+#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
579+#PURPOSE. See the GNU General Public License for more details.
580+
581+#You should have received a copy of the GNU General Public License along
582+#with this program. If not, see <http://www.gnu.org/licenses/>.
583+
584+import datetime
585+import re
586+import subprocess
587+import sys
588+from launchpadlib.errors import HTTPError
589+
590+
591+from quickly import configurationhandler
592+from quickly import launchpadaccess
593+from internal import quicklyutils
594+from quickly import templatetools
595+
596+import gettext
597+from gettext import gettext as _
598+
599+#set domain text
600+gettext.textdomain('quickly')
601+
602+class ppa_not_found(Exception):
603+ pass
604+class not_ppa_owner(Exception):
605+ pass
606+class user_team_not_found(Exception):
607+ pass
608+class invalid_versionning_scheme(Exception):
609+ def __init__(self, msg):
610+ self.msg = msg
611+ def __str__(self):
612+ return repr(self.msg)
613+class invalid_version_in_setup(Exception):
614+ def __init__(self, msg):
615+ self.msg = msg
616+ def __str__(self):
617+ return repr(self.msg)
618+
619+class DomainLevel:
620+ NONE=0
621+ WARNING=1
622+ ERROR=2
623+
624+def _continue_if_errors(err_output, warn_output, return_code,
625+ ask_on_warn_or_error):
626+ """print existing error and warning"""
627+
628+ if err_output:
629+ print #finish the current line
630+ print ('----------------------------------')
631+ print _('Command returned some ERRORS:')
632+ print ('----------------------------------')
633+ print ('\n'.join(err_output))
634+ print ('----------------------------------')
635+ if warn_output:
636+ # seek if not uneeded warning (noise from DistUtilsExtra.auto)
637+ # line following the warning should be " …"
638+ line_number = 0
639+ for line in warn_output:
640+ if (re.match(".*not recognized by DistUtilsExtra.auto.*", line)):
641+ try:
642+ if not re.match(' .*', warn_output[line_number + 1]):
643+ warn_output.remove(line)
644+ line_number -= 1
645+ except IndexError:
646+ warn_output.remove(line)
647+ line_number -= 1
648+ line_number += 1
649+ # if still something, print it
650+ if warn_output:
651+ if not err_output:
652+ print #finish the current line
653+ print _('Command returned some WARNINGS:')
654+ print ('----------------------------------')
655+ print ('\n'.join(warn_output))
656+ print ('----------------------------------')
657+ if ((err_output or warn_output) and ask_on_warn_or_error
658+ and return_code == 0):
659+ if not 'y' in raw_input("Do you want to continue (this is not safe!)? y/[n]: "):
660+ return(4)
661+ return return_code
662+
663+def _filter_out(line, output_domain, err_output, warn_output):
664+ '''filter output dispatching right domain'''
665+
666+ if 'ERR' in line:
667+ output_domain = DomainLevel.ERROR
668+ elif 'WARN' in line:
669+ output_domain = DomainLevel.WARNING
670+ elif not line.startswith(' '):
671+ output_domain = DomainLevel.NONE
672+ if '[not found]' in line:
673+ output_domain = DomainLevel.WARNING
674+ if output_domain == DomainLevel.ERROR:
675+ # only add once an error
676+ if not line in err_output:
677+ err_output.append(line)
678+ elif output_domain == DomainLevel.WARNING:
679+ # only add once a warning
680+ if not line in warn_output:
681+ # filter bad output from dpkg-buildpackage (on stderr) and p-d-e auto
682+ if not(re.match(' .*\.pot', line)
683+ or re.match(' .*\.in', line)
684+ or re.match(' dpkg-genchanges >.*', line)
685+ # FIXME: this warning is temporary: should be withed in p-d-e
686+ or re.match('.*XS-Python-Version and XB-Python-Version.*', line)):
687+ warn_output.append(line)
688+ else:
689+ sys.stdout.write('.')
690+ return (output_domain, err_output, warn_output)
691+
692+
693+def _exec_and_log_errors(command, ask_on_warn_or_error=False):
694+ '''exec the giving command and hide output if not in verbose mode'''
695+
696+ if templatetools.in_verbose_mode():
697+ return(subprocess.call(command))
698+ else:
699+ proc = subprocess.Popen(command, stdout=subprocess.PIPE,
700+ stderr=subprocess.PIPE)
701+ stdout_domain = DomainLevel.NONE
702+ stderr_domain = DomainLevel.NONE
703+ err_output = []
704+ warn_output = []
705+ while True:
706+ line_stdout = proc.stdout.readline().rstrip()
707+ line_stderr = proc.stderr.readline().rstrip()
708+ # filter stderr
709+ if line_stderr:
710+ (stderr_domain, err_output, warn_output) = _filter_out(line_stderr, stderr_domain, err_output, warn_output)
711+
712+ if not line_stdout:
713+ # don't replace by if proc.poll() as the output can be empty
714+ if proc.poll() != None:
715+ break
716+ # filter stdout
717+ else:
718+ (stdout_domain, err_output, warn_output) = _filter_out(line_stdout, stdout_domain, err_output, warn_output)
719+
720+ return(_continue_if_errors(err_output, warn_output, proc.returncode,
721+ ask_on_warn_or_error))
722+
723+
724+def updatepackaging(changelog=None, no_changelog=False):
725+ """create or update a package using python-mkdebian.
726+
727+ Commit after the first packaging creation"""
728+
729+ if not changelog:
730+ changelog = []
731+ command = ['python-mkdebian', '--force-control']
732+ if no_changelog:
733+ command.append("--no-changelog")
734+ for message in changelog:
735+ command.extend(["--changelog", message])
736+ if not configurationhandler.project_config:
737+ configurationhandler.loadConfig()
738+ try:
739+ dependencies = [elem.strip() for elem
740+ in configurationhandler.project_config['dependencies'].split(',')
741+ if elem]
742+ except KeyError:
743+ dependencies = []
744+
745+ # Hardcode the Flash plugin as a dependency, because it won't
746+ # get noticed by the autodetector.
747+ dependencies += ["flashplugin-installer"]
748+ for dep in dependencies:
749+ command.extend(["--dependency", dep])
750+
751+ try:
752+ distribution = configurationhandler.project_config['target_distribution']
753+ command.extend(["--distribution", distribution])
754+ except KeyError:
755+ pass # Distribution has not been set by user, let python-mkdebian decide what it should be
756+
757+
758+ return_code = _exec_and_log_errors(command, True)
759+ if return_code != 0:
760+ print _("An error has occurred when creating debian packaging")
761+ return(return_code)
762+
763+ print _("Ubuntu packaging created in debian/")
764+
765+ # check if first python-mkdebian (debian/ creation) to commit it
766+ # that means debian/ under unknown
767+ bzr_instance = subprocess.Popen(["bzr", "status"], stdout=subprocess.PIPE)
768+ bzr_status, err = bzr_instance.communicate()
769+ if bzr_instance.returncode != 0:
770+ return(bzr_instance.returncode)
771+
772+ if re.match('(.|\n)*unknown:\n.*debian/(.|\n)*', bzr_status):
773+ return_code = filter_exec_command(["bzr", "add"])
774+ if return_code == 0:
775+ return_code = filter_exec_command(["bzr", "commit", "-m", 'Creating ubuntu package'])
776+
777+ return(return_code)
778+
779+
780+def filter_exec_command(command):
781+ ''' Build either a source or a binary package'''
782+
783+ return(_exec_and_log_errors(command, False))
784+
785+
786+def shell_complete_ppa(ppa_to_complete):
787+ ''' Complete from available ppas '''
788+
789+ # connect to LP and get ppa to complete
790+ try:
791+ launchpad = launchpadaccess.initialize_lpi(False)
792+ except launchpadaccess.launchpad_connection_error:
793+ sys.exit(0)
794+ available_ppas = []
795+ if launchpad:
796+ try:
797+ (ppa_user, ppa_name) = get_ppa_parameters(launchpad, ppa_to_complete)
798+ except user_team_not_found:
799+ pass
800+ else:
801+ for current_ppa_name, current_ppa_displayname in get_all_ppas(launchpad, ppa_user):
802+ # print user/ppa form
803+ available_ppas.append("%s/%s" % (ppa_user.name, current_ppa_name))
804+ # if it's the user, print in addition just "ppa_name" syntax
805+ if ppa_user.name == launchpad.me.name:
806+ available_ppas.append(current_ppa_name)
807+ # if we don't have provided a team, show all teams were we are member off
808+ if not '/' in ppa_to_complete:
809+ team = [mem.team for mem in launchpad.me.memberships_details if mem.status in ("Approved", "Administrator")]
810+ for elem in team:
811+ available_ppas.append(elem.name + '/')
812+ return available_ppas
813+
814+def get_ppa_parameters(launchpad, full_ppa_name):
815+ ''' Check if we can catch good parameters for specified ppa in form user/ppa or ppa '''
816+
817+ if '/' in full_ppa_name:
818+ ppa_user_name = full_ppa_name.split('/')[0]
819+ ppa_name = full_ppa_name.split('/')[1]
820+ # check that we are in the team/or that we are the user
821+ try:
822+ lp_ppa_user = launchpad.people[ppa_user_name]
823+ if lp_ppa_user.name == launchpad.me.name:
824+ ppa_user = launchpad.me
825+ else:
826+ # check if we are a member of this team
827+ team = [mem.team for mem in launchpad.me.memberships_details if mem.status in ("Approved", "Administrator") and mem.team.name == ppa_user_name]
828+ if team:
829+ ppa_user = team[0]
830+ else:
831+ raise not_ppa_owner(ppa_user_name)
832+ except KeyError:
833+ raise user_team_not_found(ppa_user_name)
834+ else:
835+ ppa_user = launchpad.me
836+ ppa_name = full_ppa_name
837+ return(ppa_user, ppa_name)
838+
839+def choose_ppa(launchpad, ppa_name=None):
840+ '''Look for right ppa parameters where to push the package'''
841+
842+ if not ppa_name:
843+ if not configurationhandler.project_config:
844+ configurationhandler.loadConfig()
845+ try:
846+ (ppa_user, ppa_name) = get_ppa_parameters(launchpad, configurationhandler.project_config['ppa'])
847+ except KeyError:
848+ ppa_user = launchpad.me
849+ if (launchpadaccess.lp_server == "staging"):
850+ ppa_name = 'staging'
851+ else: # default ppa
852+ ppa_name = 'ppa'
853+ else:
854+ (ppa_user, ppa_name) = get_ppa_parameters(launchpad, ppa_name)
855+ ppa_url = '%s/~%s/+archive/%s' % (launchpadaccess.LAUNCHPAD_URL, ppa_user.name, ppa_name)
856+ dput_ppa_name = 'ppa:%s/%s' % (ppa_user.name, ppa_name)
857+ return (ppa_user, ppa_name, dput_ppa_name, ppa_url.encode('UTF-8'))
858+
859+def push_to_ppa(dput_ppa_name, changes_file, keyid=None):
860+ """ Push some code to a ppa """
861+
862+ # creating local binary package
863+ buildcommand = ["dpkg-buildpackage", "-S", "-I.bzr"]
864+ if keyid:
865+ buildcommand.append("-k%s" % keyid)
866+ return_code = filter_exec_command(buildcommand)
867+ if return_code != 0:
868+ print _("ERROR: an error occurred during source package creation")
869+ return(return_code)
870+ # now, pushing it to launchpad personal ppa (or team later)
871+ return_code = subprocess.call(["dput", dput_ppa_name, changes_file])
872+ if return_code != 0:
873+ print _("ERROR: an error occurred during source upload to launchpad")
874+ return(return_code)
875+ return(0)
876+
877+def get_all_ppas(launchpad, lp_team_or_user):
878+ """ get all from a team or users
879+
880+ Return list of tuples (ppa_name, ppa_display_name)"""
881+
882+ ppa_list = []
883+ for ppa in lp_team_or_user.ppas:
884+ ppa_list.append((ppa.name, ppa.displayname))
885+ return ppa_list
886+
887+def check_and_return_ppaname(launchpad, lp_team_or_user, ppa_name):
888+ """ check whether ppa exists using its name or display name for the lp team or user
889+
890+ return formated ppaname (not display name)"""
891+
892+ # check that the owner really has this ppa:
893+ ppa_found = False
894+ for current_ppa_name, current_ppa_displayname in get_all_ppas(launchpad, lp_team_or_user):
895+ if current_ppa_name == ppa_name or current_ppa_displayname == ppa_name:
896+ ppa_found = True
897+ break
898+ if not ppa_found:
899+ raise ppa_not_found('ppa:%s:%s' % (lp_team_or_user.name, ppa_name.encode('UTF-8')))
900+ return(current_ppa_name)
901+
902+def updateversion(proposed_version=None, sharing=False):
903+ '''Update versioning with year.month, handling intermediate release'''
904+
905+ if proposed_version:
906+ # check manual versionning is correct
907+ try:
908+ for number in proposed_version.split('.'):
909+ float(number)
910+ except ValueError:
911+ msg = _("Release version specified in command arguments is not a " \
912+ "valid version scheme like 'x(.y)(.z)'.")
913+ raise invalid_versionning_scheme(msg)
914+ new_version = proposed_version
915+ else:
916+ # get previous value
917+ try:
918+ old_version = quicklyutils.get_setup_value('version')
919+ except quicklyutils.cant_deal_with_setup_value:
920+ msg = _("No previous version found in setup.py. Put one please")
921+ raise invalid_version_in_setup(msg)
922+
923+ # sharing only add -publicX to last release, no other update, no bumping
924+ if sharing:
925+ splitted_release_version = old_version.split("-public")
926+ if len(splitted_release_version) > 1:
927+ try:
928+ share_version = float(splitted_release_version[1])
929+ except ValueError:
930+ msg = _("Share version specified after -public in "\
931+ "setup.py is not a valid number: %s") \
932+ % splitted_release_version[1]
933+ raise invalid_versionning_scheme(msg)
934+ new_version = splitted_release_version[0] + '-public' + \
935+ str(int(share_version + 1))
936+ else:
937+ new_version = old_version + "-public1"
938+
939+ # automatically version to year.month(.subversion)
940+ else:
941+ base_version = datetime.datetime.now().strftime("%y.%m")
942+ if base_version in old_version:
943+ try:
944+ # try to get a minor version, removing -public if one
945+ (year, month, minor_version) = old_version.split('.')
946+ minor_version = minor_version.split('-public')[0]
947+ try:
948+ minor_version = float(minor_version)
949+ except ValueError:
950+ msg = _("Minor version specified in setup.py is not a " \
951+ "valid number: %s. Fix this or specify a " \
952+ "version as release command line argument") \
953+ % minor_version
954+ raise invalid_versionning_scheme(msg)
955+ new_version = base_version + '.' + str(int(minor_version + 1))
956+
957+ except ValueError:
958+ # no minor version, bump to first one (be careful,
959+ # old_version may contain -publicX)
960+ new_version = base_version + '.1'
961+
962+ else:
963+ # new year/month
964+ new_version = base_version
965+
966+ # write release version to setup.py and update it in aboutdialog
967+ quicklyutils.set_setup_value('version', new_version)
968+ about_dialog_file_name = quicklyutils.get_about_file_name()
969+ if about_dialog_file_name:
970+ quicklyutils.change_xml_elem(about_dialog_file_name, "object/property",
971+ "name", "version", new_version, {})
972+
973+ return new_version
974
975=== added file 'data/templates/flash-game/internal/quicklyutils.py'
976--- data/templates/flash-game/internal/quicklyutils.py 1970-01-01 00:00:00 +0000
977+++ data/templates/flash-game/internal/quicklyutils.py 2011-01-03 19:29:40 +0000
978@@ -0,0 +1,411 @@
979+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
980+# Copyright 2009 Didier Roche
981+#
982+# This file is part of Quickly ubuntu-application template
983+#
984+#This program is free software: you can redistribute it and/or modify it
985+#under the terms of the GNU General Public License version 3, as published
986+#by the Free Software Foundation.
987+
988+#This program is distributed in the hope that it will be useful, but
989+#WITHOUT ANY WARRANTY; without even the implied warranties of
990+#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
991+#PURPOSE. See the GNU General Public License for more details.
992+
993+#You should have received a copy of the GNU General Public License along
994+#with this program. If not, see <http://www.gnu.org/licenses/>.
995+
996+import os
997+import re
998+import sys
999+import subprocess
1000+from xml.etree import ElementTree as etree
1001+
1002+import gettext
1003+from gettext import gettext as _
1004+#set domain text
1005+gettext.textdomain('quickly')
1006+
1007+from quickly import configurationhandler
1008+from quickly import templatetools
1009+
1010+class cant_deal_with_setup_value(Exception):
1011+ pass
1012+class gpg_error(Exception):
1013+ def __init__(self, message):
1014+ self.message = message
1015+ def __str__(self):
1016+ return repr(self.message)
1017+
1018+def conventional_names(name):
1019+ sentence_name = templatetools.get_sentence_name(name)
1020+ camel_case_name = templatetools.get_camel_case_name(name)
1021+ return sentence_name, camel_case_name
1022+
1023+def file_from_template(template_dir, template_file, target_dir, substitutions=[], rename = True):
1024+
1025+ if not os.path.isfile(os.path.join(template_dir, template_file)):
1026+ return
1027+ target_file = os.path.basename(template_file) # to get only file name (template_file can be internal/file)
1028+ if rename:
1029+ for s in substitutions:
1030+ pattern, sub = s
1031+ target_file = target_file.replace(pattern,sub)
1032+
1033+ fin = open(os.path.join(template_dir, template_file),'r')
1034+ file_contents = fin.read()
1035+ for s in substitutions:
1036+ pattern, sub = s
1037+ file_contents = file_contents.replace(pattern,sub)
1038+
1039+ target_path = os.path.join(target_dir, target_file)
1040+ if os.path.exists(target_path):
1041+ print _("Failed to add file to project\n cannot add: %s - this file already exists." % target_path)
1042+ sys.exit(4)
1043+
1044+ fout = open(target_path, 'w')
1045+ fout.write(file_contents)
1046+ fout.flush()
1047+ fout.close()
1048+ fin.close()
1049+
1050+def get_setup_value(key):
1051+ """ get value from setup.py file.
1052+
1053+ raise cant_deal_with_setup_value if nothing found
1054+ : return found value"""
1055+
1056+ result = None
1057+ in_setup = False
1058+ try:
1059+ fsetup = file('setup.py', 'r')
1060+ for line in fsetup:
1061+ if in_setup:
1062+ fields = line.split('=') # Separate variable from value
1063+ if key == fields[0].strip(): # if key found and not commented
1064+ result = fields[1].partition(',')[0].strip()
1065+ result = result[1:-1]
1066+ break
1067+ if "setup(" in line:
1068+ in_setup = True
1069+ # if end of the function, finished
1070+ if in_setup and ')' in line:
1071+ in_setup = False
1072+ fsetup.close()
1073+ except (OSError, IOError), e:
1074+ print _("ERROR: Can't load setup.py file")
1075+ sys.exit(1)
1076+
1077+ if result is None:
1078+ raise cant_deal_with_setup_value()
1079+ return result
1080+
1081+def set_setup_value(key, value):
1082+ """ set value from setup.py file
1083+
1084+ it adds new key in the setup() function if not found.
1085+ it uncomments a commented value if changed.
1086+
1087+ exit with 0 if everything's all right
1088+ """
1089+
1090+ has_changed_something = False
1091+ in_setup = False
1092+ try:
1093+ fsetup = file('setup.py', 'r')
1094+ fdest = file(fsetup.name + '.new', 'w')
1095+ for line in fsetup:
1096+ if in_setup:
1097+ fields = line.split('=') # Separate variable from value
1098+ if key == fields[0].strip() or "#%s" % key == fields[0].strip():
1099+ # add new value, uncommenting it if present
1100+ line = "%s='%s',\n" % (fields[0].replace('#',''), value)
1101+ has_changed_something = True
1102+
1103+ if "setup(" in line:
1104+ in_setup = True
1105+ # add it if the value was not present and reach end of setup() function
1106+ if not has_changed_something and in_setup and ")" in line:
1107+ fdest.write(" %s='%s',\n" % (key, value))
1108+ in_setup = False
1109+ fdest.write(line)
1110+
1111+ fdest.close()
1112+ fsetup.close()
1113+ os.rename(fdest.name, fsetup.name)
1114+ except (OSError, IOError), e:
1115+ print _("ERROR: Can't load setup.py file")
1116+ sys.exit(1)
1117+
1118+ return 0
1119+
1120+def get_about_file_name():
1121+ """Get about file name if exists"""
1122+ if not configurationhandler.project_config:
1123+ configurationhandler.loadConfig()
1124+ about_file_name = "data/ui/About%sDialog.ui" % templatetools.get_camel_case_name(configurationhandler.project_config['project'])
1125+ if not os.path.isfile(about_file_name):
1126+ return None
1127+ return about_file_name
1128+
1129+def change_xml_elem(xml_file, path, attribute_name, attribute_value, value, attributes_if_new):
1130+ """change an elem in a xml tree and save it
1131+
1132+ xml_file: url of the xml file
1133+ path -> path to tag to change
1134+ attribute_value -> attribute name to match
1135+ attribute_value -> attribute value to match
1136+ value -> new value
1137+ attributes_if_new -> dictionnary of additional attributes if we create a new node"""
1138+ found = False
1139+ xml_tree = etree.parse(xml_file)
1140+ if not attributes_if_new:
1141+ attributes_if_new = {}
1142+ attributes_if_new[attribute_name] = attribute_value
1143+ for node in xml_tree.findall(path):
1144+ if not attribute_name or node.attrib[attribute_name] == attribute_value:
1145+ node.text = value
1146+ found = True
1147+ if not found:
1148+ parent_node = "/".join(path.split('/')[:-1])
1149+ child_node = path.split('/')[-1]
1150+ new_node = etree.Element(child_node, attributes_if_new)
1151+ new_node.text = value
1152+ xml_tree.find(parent_node).insert(0, new_node)
1153+ xml_tree.write(xml_file + '.new')
1154+ os.rename(xml_file + '.new', xml_file)
1155+
1156+def collect_commit_messages(previous_version):
1157+ '''Collect commit messages from last revision'''
1158+
1159+ bzr_command = ['bzr', 'log']
1160+ if previous_version:
1161+ bzr_command.extend(['-r', 'tag:%s..' % previous_version])
1162+ else:
1163+ previous_version = ''
1164+ bzr_instance = subprocess.Popen(bzr_command, stdout=subprocess.PIPE)
1165+ result, err = bzr_instance.communicate()
1166+
1167+ if bzr_instance.returncode != 0:
1168+ return(None)
1169+
1170+ changelog = []
1171+ buffered_message = ""
1172+ collect_switch = False
1173+ uncollect_msg = (_('quickly saved'), _('commit before release'))
1174+ for line in result.splitlines():
1175+ #print buffered_message
1176+ if line == 'message:':
1177+ collect_switch = True
1178+ continue
1179+ elif '----------------------' in line:
1180+ if buffered_message:
1181+ changelog.append(buffered_message.strip())
1182+ buffered_message = ""
1183+ collect_switch = False
1184+ elif line == 'tags: %s' % previous_version:
1185+ break
1186+ if collect_switch and not line.strip() in uncollect_msg:
1187+ buffered_message +=' %s' % line
1188+ return(changelog)
1189+
1190+
1191+def get_quickly_editors():
1192+ '''Return prefered editor for ubuntu-application template'''
1193+
1194+ editor = "gedit"
1195+ default_editor = os.environ.get("EDITOR")
1196+ if not default_editor:
1197+ default_editor = os.environ.get("SELECTED_EDITOR")
1198+ if default_editor:
1199+ editor = default_editor
1200+ return editor
1201+
1202+
1203+def take_email_from_string(value):
1204+ '''Try to take an email from a string'''
1205+
1206+ if value is not None:
1207+ result = re.match("(.*[< ]|^)(.+@[^ >]+\.[^ >]+).*", value)
1208+ if result:
1209+ return result.groups()[1]
1210+ return value
1211+
1212+def get_all_emails(launchpad=None):
1213+ '''Return a list with all available email in preference order'''
1214+
1215+ email_list = []
1216+ email_list.append(take_email_from_string(os.getenv("DEBEMAIL")))
1217+
1218+ bzr_instance = subprocess.Popen(["bzr", "whoami"], stdout=subprocess.PIPE)
1219+ bzr_user, err = bzr_instance.communicate()
1220+ if bzr_instance.returncode == 0:
1221+ email_list.append(take_email_from_string(bzr_user))
1222+ email_list.append(take_email_from_string(os.getenv("EMAIL")))
1223+
1224+ # those information can be missing if there were no packaging or license
1225+ # command before
1226+ try:
1227+ email_list.append(take_email_from_string(get_setup_value('author_email')))
1228+ except cant_deal_with_setup_value:
1229+ pass
1230+
1231+ # AUTHORS
1232+ fauthors_name = 'AUTHORS'
1233+ for line in file(fauthors_name, 'r'):
1234+ if not "<Your E-mail>" in line:
1235+ email_list.append(take_email_from_string(line))
1236+
1237+ # LP adresses
1238+ if launchpad:
1239+ email_list.append(launchpad.preferred_email_address.email())
1240+
1241+ # gpg key (if one)
1242+ gpg_instance = subprocess.Popen(['gpg', '--list-secret-keys', '--with-colon'], stdout=subprocess.PIPE)
1243+ result, err = gpg_instance.communicate()
1244+ if gpg_instance.returncode != 0:
1245+ raise gpg_error(err)
1246+ for line in result.splitlines():
1247+ if 'sec' in line or 'uid' in line:
1248+ email_list.append(take_email_from_string(line.split(':')[9]))
1249+
1250+ # return email list without None elem
1251+ return [email for email in email_list if email]
1252+
1253+def upload_gpg_key_to_launchpad(key_id):
1254+ '''push gpg key to launchpad not yet possible'''
1255+
1256+ raise gpg_error(_("There is no GPG key detected for your Launchpad "
1257+ "account. Please upload one as you can read on the " \
1258+ "tutorial"))
1259+
1260+def create_gpg_key(name, email):
1261+ '''create a gpg key and return the corresponding id'''
1262+
1263+ if not 'y' in raw_input("It seems you don't have a gpg key on your " \
1264+ "computer. Do you want to create one (this may " \
1265+ "take a while)? y/[n]: "):
1266+ raise gpg_error(_("You choosed to not create your GPG key."))
1267+ key_generate = '''Key-Type: RSA
1268+Key-Length: 1024
1269+Name-Real: %s
1270+Name-Email: %s
1271+Expire-Date: 0
1272+%%commit''' % (name, email)
1273+ gpg_instance = subprocess.Popen(['gpg', '--gen-key', '--batch'],
1274+ stdin=subprocess.PIPE,
1275+ stdout=subprocess.PIPE)
1276+ result, err = gpg_instance.communicate(key_generate.encode('utf-8'))
1277+ if gpg_instance.returncode != 0:
1278+ raise gpg_error(err)
1279+
1280+ gpg_instance = subprocess.Popen(['gpg', '--list-secret-keys', '--with-colon'], stdout=subprocess.PIPE)
1281+ result, err = gpg_instance.communicate()
1282+ if gpg_instance.returncode != 0:
1283+ raise gpg_error(err)
1284+ secret_key_id = None
1285+ for line in result.splitlines():
1286+ if 'sec' in line:
1287+ secret_key_id = line.split(':')[4][-8:]
1288+ if not secret_key_id:
1289+ raise gpg_error(_("Can't create GPG key. Try to create it yourself."))
1290+
1291+ # TODO: to be able to upload key to LP
1292+ raw_input("Your gpg key has been create. You have to upload it to " \
1293+ "Launchpad. Guidance is provided in Launchpad help. " \
1294+ "Press any key once done.")
1295+
1296+ return secret_key_id
1297+
1298+def get_right_gpg_key_id(launchpad):
1299+ '''Try to fech (and explain how to upload) right GPG key'''
1300+
1301+ verbose = templatetools.in_verbose_mode()
1302+ prefered_emails = get_all_emails()
1303+ if not prefered_emails:
1304+ raise gpg_error(_("Can't sign the package as no adress email found. " \
1305+ "Fulfill the AUTHORS file with name <emailadress> " \
1306+ "or export DEBEMAIL/EMAIL."))
1307+ if verbose:
1308+ print prefered_emails
1309+
1310+ gpg_instance = subprocess.Popen(['gpg', '--list-secret-keys', '--with-colon'], stdout=subprocess.PIPE)
1311+ result, err = gpg_instance.communicate()
1312+ if gpg_instance.returncode != 0:
1313+ raise gpg_error(err)
1314+ candidate_key_ids = {}
1315+ for line in result.splitlines():
1316+ if 'sec' in line:
1317+ secret_key_id = line.split(':')[4][-8:]
1318+ if verbose:
1319+ print "found secret gpg key. id: %s" % secret_key_id
1320+ candidate_email = take_email_from_string(line.split(':')[9])
1321+ if verbose:
1322+ print "candidate email: %s" % candidate_email
1323+ if candidate_email and candidate_email in prefered_emails:
1324+ # create candidate_key_ids[candidate_email] if needed
1325+ try:
1326+ candidate_key_ids[candidate_email]
1327+ except KeyError:
1328+ candidate_key_ids[candidate_email] = []
1329+ candidate_key_ids[candidate_email].append(secret_key_id)
1330+ if not candidate_key_ids:
1331+ candidate_key_ids[prefered_emails[0]] = [create_gpg_key(
1332+ launchpad.me.display_name, prefered_emails[0])]
1333+
1334+ if verbose:
1335+ print "candidate_key_ids: %s" % candidate_key_ids
1336+
1337+ # reorder key_id by email adress
1338+ prefered_key_ids = []
1339+ for email in prefered_emails:
1340+ try:
1341+ prefered_key_ids.append((candidate_key_ids[email], email))
1342+ except KeyError:
1343+ pass
1344+ if not prefered_key_ids:
1345+ raise gpg_error(_("GPG keys found matching no prefered email. You " \
1346+ "can export DEBEMAIL or put it in AUTHORS file " \
1347+ "one matching your local gpg key."))
1348+ if verbose:
1349+ print "prefered_key_ids: %s" % prefered_key_ids
1350+
1351+ # get from launchpad the gpg key
1352+ launchpad_key_ids = []
1353+ for key in launchpad.me.gpg_keys:
1354+ launchpad_key_ids.append(key.keyid)
1355+
1356+ if not launchpad_key_ids:
1357+ upload_gpg_key_to_launchpad(prefered_key_ids[0])
1358+ launchpad_key_ids = [prefered_key_ids[0]]
1359+
1360+ if verbose:
1361+ print "launchpad_key_ids: %s" % launchpad_key_ids
1362+
1363+ # take first match:
1364+ for key_ids, email in prefered_key_ids:
1365+ for key_id in key_ids:
1366+ if key_id in launchpad_key_ids:
1367+ # export env variable for changelog and signing
1368+ author_name = launchpad.me.display_name.encode('UTF-8')
1369+ if not os.getenv('DEBFULLNAME'):
1370+ os.putenv('DEBFULLNAME', author_name)
1371+ if not os.getenv('DEBEMAIL'):
1372+ os.putenv('DEBEMAIL', email)
1373+ if verbose:
1374+ print "Selected key_id: %s, author: %s, email: %s" % (key_id, author_name, email)
1375+ # set upstream author and email
1376+ try:
1377+ get_setup_value('author')
1378+ except cant_deal_with_setup_value:
1379+ set_setup_value('author', author_name)
1380+ try:
1381+ get_setup_value('author_email')
1382+ except cant_deal_with_setup_value:
1383+ set_setup_value('author_email', email)
1384+ return key_id
1385+
1386+ # shouldn't happen as other errors are caught
1387+ raise gpg_error(_("No gpg key set matching launchpad one found.'"))
1388+
1389+
1390
1391=== added file 'data/templates/flash-game/package.py'
1392--- data/templates/flash-game/package.py 1970-01-01 00:00:00 +0000
1393+++ data/templates/flash-game/package.py 2011-01-03 19:29:40 +0000
1394@@ -0,0 +1,73 @@
1395+#!/usr/bin/python
1396+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1397+# Copyright 2009 Didier Roche
1398+#
1399+# This file is part of Quickly ubuntu-application template
1400+#
1401+#This program is free software: you can redistribute it and/or modify it
1402+#under the terms of the GNU General Public License version 3, as published
1403+#by the Free Software Foundation.
1404+
1405+#This program is distributed in the hope that it will be useful, but
1406+#WITHOUT ANY WARRANTY; without even the implied warranties of
1407+#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1408+#PURPOSE. See the GNU General Public License for more details.
1409+
1410+#You should have received a copy of the GNU General Public License along
1411+#with this program. If not, see <http://www.gnu.org/licenses/>.
1412+
1413+import sys
1414+import subprocess
1415+
1416+import gettext
1417+from gettext import gettext as _
1418+gettext.textdomain('quickly')
1419+
1420+from internal import quicklyutils, packaging
1421+from quickly import templatetools, configurationhandler
1422+
1423+
1424+def help():
1425+ print _("""Usage:
1426+$quickly package
1427+
1428+Creates a debian file (deb) from your project. Before running
1429+the package command you can edit the Icon and Category entry of *.desktop.in
1430+file, where * is the name of your project.
1431+
1432+Note that if you didn't run quickly release, quickly share
1433+or quickly change-lp-project you may miss the name, email in
1434+setup.py. You can edit them if you don't want to use any of these
1435+commands afterwards. Those changes are not a mandatory at all for
1436+testing purpose.
1437+""")
1438+templatetools.handle_additional_parameters(sys.argv, help)
1439+
1440+# retrieve useful information
1441+if not configurationhandler.project_config:
1442+ configurationhandler.loadConfig()
1443+project_name = configurationhandler.project_config['project']
1444+
1445+try:
1446+ release_version = quicklyutils.get_setup_value('version')
1447+except quicklyutils.cant_deal_with_setup_value:
1448+ print _("Release version not found in setup.py.")
1449+
1450+
1451+# creation/update debian packaging
1452+if packaging.updatepackaging(no_changelog=True) != 0:
1453+ print _("ERROR: can't create or update ubuntu package")
1454+ sys.exit(1)
1455+
1456+# creating local binary package
1457+return_code = packaging.filter_exec_command(["dpkg-buildpackage", "-tc",
1458+ "-I.bzr", "-us", "-uc"])
1459+
1460+if return_code == 0:
1461+ print _("Ubuntu package has been successfully created in ../%s_%s_all.deb") % (project_name, release_version)
1462+else:
1463+ print _("An error has occurred during package building")
1464+
1465+sys.exit(return_code)
1466+
1467+
1468
1469=== added directory 'data/templates/flash-game/project_root'
1470=== added file 'data/templates/flash-game/project_root/AUTHORS'
1471--- data/templates/flash-game/project_root/AUTHORS 1970-01-01 00:00:00 +0000
1472+++ data/templates/flash-game/project_root/AUTHORS 2011-01-03 19:29:40 +0000
1473@@ -0,0 +1,1 @@
1474+Copyright (C) YYYY <Your Name> <Your E-mail>
1475
1476=== added directory 'data/templates/flash-game/project_root/bin'
1477=== added file 'data/templates/flash-game/project_root/bin/project_name'
1478--- data/templates/flash-game/project_root/bin/project_name 1970-01-01 00:00:00 +0000
1479+++ data/templates/flash-game/project_root/bin/project_name 2011-01-03 19:29:40 +0000
1480@@ -0,0 +1,71 @@
1481+#!/usr/bin/python
1482+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1483+### BEGIN LICENSE
1484+# This file is in the public domain
1485+### END LICENSE
1486+
1487+###########################################################################
1488+# SWF file details
1489+# Edit these lines as appropriate
1490+###########################################################################
1491+
1492+GAME_NAME = "sentence_name"
1493+WINDOW_SIZE = (swf_width, swf_height)
1494+
1495+
1496+###########################################################################
1497+# No need to edit below here
1498+###########################################################################
1499+
1500+
1501+import webkit, gtk, gio
1502+import json, os
1503+
1504+# Where your project will look for your data (for instance, images and ui
1505+# files). By default, this is ../data, relative your trunk layout
1506+__python_name_data_directory__ = '../data/'
1507+
1508+def get_data_file(*path_segments):
1509+ """Get the full path to a data file.
1510+
1511+ Returns the path to a file underneath the data directory (as defined by
1512+ `get_data_path`). Equivalent to os.path.join(get_data_path(),
1513+ *path_segments).
1514+ """
1515+ return os.path.join(get_data_path(), *path_segments)
1516+
1517+
1518+def get_data_path():
1519+ """Retrieve project_name data path
1520+
1521+ This path is by default <python_name_lib_path>/../data/ in trunk
1522+ and /usr/share/project_name in an installed version but this path
1523+ is specified at installation time.
1524+ """
1525+
1526+ # Get pathname absolute or relative.
1527+ path = os.path.join(
1528+ os.path.dirname(__file__), __python_name_data_directory__)
1529+
1530+ abs_data_path = os.path.abspath(path)
1531+ if not os.path.exists(abs_data_path):
1532+ raise project_path_not_found
1533+
1534+ return abs_data_path
1535+
1536+
1537+if __name__ == "__main__":
1538+ w = gtk.Window()
1539+ v = webkit.WebView()
1540+ w.add(v)
1541+ w.show_all() # have to have this before set_size_request
1542+ w.connect("destroy", lambda q: gtk.main_quit())
1543+ w.set_title(GAME_NAME)
1544+ htmlfp = gio.File(get_data_file('index.html'))
1545+ uri = htmlfp.get_uri()
1546+ html, _, _ = htmlfp.load_contents()
1547+ v.load_html_string(html, uri)
1548+ w.set_size_request(*WINDOW_SIZE)
1549+ v.queue_resize() # force a reallocation, https://bugs.webkit.org/show_bug.cgi?id=47742
1550+ gtk.main()
1551+
1552
1553=== added directory 'data/templates/flash-game/project_root/data'
1554=== added file 'data/templates/flash-game/project_root/data/index.html'
1555--- data/templates/flash-game/project_root/data/index.html 1970-01-01 00:00:00 +0000
1556+++ data/templates/flash-game/project_root/data/index.html 2011-01-03 19:29:40 +0000
1557@@ -0,0 +1,9 @@
1558+<html>
1559+<head>
1560+<style>
1561+html, body { margin: 0; padding: 0; }
1562+</style>
1563+</head>
1564+<body><object width="100%" height="100%"><param name="movie" value="game.swf"></param></object></body>
1565+</html>
1566+
1567
1568=== added file 'data/templates/flash-game/project_root/project_name.desktop.in'
1569--- data/templates/flash-game/project_root/project_name.desktop.in 1970-01-01 00:00:00 +0000
1570+++ data/templates/flash-game/project_root/project_name.desktop.in 2011-01-03 19:29:40 +0000
1571@@ -0,0 +1,8 @@
1572+[Desktop Entry]
1573+_Name=sentence_name
1574+_Comment=camel_case_name application
1575+Categories=GNOME;Utility;
1576+Exec=project_name
1577+Icon=project_name
1578+Terminal=false
1579+Type=Application
1580
1581=== added file 'data/templates/flash-game/project_root/setup.py'
1582--- data/templates/flash-game/project_root/setup.py 1970-01-01 00:00:00 +0000
1583+++ data/templates/flash-game/project_root/setup.py 2011-01-03 19:29:40 +0000
1584@@ -0,0 +1,69 @@
1585+#!/usr/bin/env python
1586+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1587+### BEGIN LICENSE
1588+# This file is in the public domain
1589+### END LICENSE
1590+
1591+###################### DO NOT TOUCH THIS (HEAD TO THE SECOND PART) ######################
1592+
1593+import os
1594+import sys
1595+
1596+try:
1597+ import DistUtilsExtra.auto
1598+except ImportError:
1599+ print >> sys.stderr, 'To build project_name you need https://launchpad.net/python-distutils-extra'
1600+ sys.exit(1)
1601+assert DistUtilsExtra.auto.__version__ >= '2.18', 'needs DistUtilsExtra.auto >= 2.18'
1602+
1603+def update_data_path(prefix, oldvalue=None):
1604+
1605+ try:
1606+ fin = file('bin/project_name', 'r')
1607+ fout = file(fin.name + '.new', 'w')
1608+
1609+ for line in fin:
1610+ fields = line.split(' = ') # Separate variable from value
1611+ if fields[0] == '__python_name_data_directory__':
1612+ # update to prefix, store oldvalue
1613+ if not oldvalue:
1614+ oldvalue = fields[1]
1615+ line = "%s = '%s'\n" % (fields[0], prefix)
1616+ else: # restore oldvalue
1617+ line = "%s = %s" % (fields[0], oldvalue)
1618+ fout.write(line)
1619+
1620+ fout.flush()
1621+ fout.close()
1622+ fin.close()
1623+ os.rename(fout.name, fin.name)
1624+ except (OSError, IOError), e:
1625+ print ("ERROR: Can't find python_name/python_nameconfig.py")
1626+ sys.exit(1)
1627+ return oldvalue
1628+
1629+
1630+class InstallAndUpdateDataDirectory(DistUtilsExtra.auto.install_auto):
1631+ def run(self):
1632+ previous_value = update_data_path(self.prefix + '/share/project_name/')
1633+ DistUtilsExtra.auto.install_auto.run(self)
1634+ update_data_path(self.prefix, previous_value)
1635+
1636+
1637+
1638+##################################################################################
1639+###################### YOU SHOULD MODIFY ONLY WHAT IS BELOW ######################
1640+##################################################################################
1641+
1642+DistUtilsExtra.auto.setup(
1643+ name='project_name',
1644+ version='0.1',
1645+ #license='GPL-3',
1646+ #author='Your Name',
1647+ #author_email='email@ubuntu.com',
1648+ #description='UI for managing …',
1649+ #long_description='Here a longer description',
1650+ #url='https://launchpad.net/project_name',
1651+ cmdclass={'install': InstallAndUpdateDataDirectory}
1652+ )
1653+

Subscribers

People subscribed via source and target branches