Added command-line utility. Refactored code to accomodate.

This commit is contained in:
Joey Payne 2014-12-19 23:50:13 +13:00
commit f7fa4cb6b6
9 changed files with 1261 additions and 865 deletions

741
command_line.py Normal file
View file

@ -0,0 +1,741 @@
'''Command line module for web2exe.'''
from utils import zip_files, join_files, log, get_temp_dir
from pycns import save_icns
from pepy.pe import PEFile
import urllib2
import re
import sys
import os
import glob
import json
import shutil
import stat
import tarfile
import zipfile
import traceback
from distutils.version import LooseVersion
from zipfile import ZipFile
from tarfile import TarFile
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from configobj import ConfigObj
inside_mac_app = getattr(sys, 'frozen', '')
if inside_mac_app:
CWD = os.path.dirname(sys.executable)
os.chdir(CWD)
else:
CWD = os.getcwd()
TEMP_DIR = get_temp_dir()
DEFAULT_DOWNLOAD_PATH = os.path.join(CWD, 'files', 'downloads')
try:
os.makedirs(DEFAULT_DOWNLOAD_PATH)
except:
pass
def get_base_url():
url = None
try:
url = open(os.path.join(CWD, 'files', 'base_url.txt')).read().strip()
except (OSError, IOError):
url = 'http://dl.node-webkit.org/v{}/'
return url
class Setting(object):
def __init__(self, name='', display_name=None, value=None,
required=False, type=None, file_types=None, *args, **kwargs):
self.name = name
self.display_name = (display_name
if display_name
else name.replace('_', ' ').capitalize())
self.value = value
self.last_value = None
self.required = required
self.type = type
self.file_types = file_types
self.default_value = kwargs.pop('default_value', None)
self.button = kwargs.pop('button', None)
self.button_callback = kwargs.pop('button_callback', None)
self.description = kwargs.pop('description', '')
self.values = kwargs.pop('values', [])
self.set_extra_attributes_from_keyword_args(kwargs)
if self.value is None:
self.value = self.default_value
self.save_path = kwargs.pop('save_path', '')
self.get_file_information_from_url()
def get_file_information_from_url(self):
if hasattr(self, 'url'):
self.file_name = self.url.split('/')[-1]
self.full_file_path = os.path.join(self.save_path, self.file_name)
self.file_ext = os.path.splitext(self.file_name)[1]
if self.file_ext == '.zip':
self.extract_class = ZipFile
self.extract_args = ()
elif self.file_ext == '.gz':
self.extract_class = TarFile.open
self.extract_args = ('r:gz',)
def save_file_path(self, version, location=None):
if location:
self.save_path = location
else:
self.save_path = self.save_path or DEFAULT_DOWNLOAD_PATH
self.get_file_information_from_url()
if self.full_file_path:
return self.full_file_path.format(version)
return ''
def extract_file_path(self, version):
if self.extract_file:
return self.extract_file.format(version)
return ''
def set_extra_attributes_from_keyword_args(self, kwargs):
for undefined_key, undefined_value in kwargs.items():
setattr(self, undefined_key, undefined_value)
def get_file_bytes(self, version):
fbytes = []
file = self.extract_class(self.save_file_path(version),
*self.extract_args)
for extract_path, dest_path in zip(self.extract_files,
self.dest_files):
new_bytes = None
try:
extract_p = extract_path.format(version)
if self.file_ext == '.gz':
new_bytes = file.extractfile(extract_p).read()
elif self.file_ext == '.zip':
new_bytes = file.read(extract_p)
except KeyError as e:
log(e)
# dirty hack to support old versions of nw
if 'no item named' in str(e):
extract_path = '/'.join(extract_path.split('/')[1:])
try:
if self.file_ext == '.gz':
new_bytes = file.extractfile(extract_path).read()
elif self.file_ext == '.zip':
new_bytes = file.read(extract_path)
except KeyError as e:
log(e)
print e
print e
if new_bytes is not None:
fbytes.append((dest_path, new_bytes))
return fbytes
def __repr__(self):
url = ''
if hasattr(self, 'url'):
url = self.url
return ('Setting: (name={}, '
'display_name={}, '
'value={}, required={}, '
'type={}, url={})').format(self.name,
self.display_name,
self.value,
self.required,
self.type,
url)
class CommandBase(object):
def __init__(self):
self.quiet = False
self.output_package_json = False
self.settings = self.get_settings()
self._project_dir = ''
self._output_dir = ''
self._progress_text = ''
self._output_err = ''
self._extract_error = ''
self.original_packagejson = {}
def update_nw_versions(self):
self.get_versions()
def get_settings(self):
config_file = os.path.join(CWD, 'files', 'settings.cfg')
contents = open(config_file).read()
contents = contents.replace('{DEFAULT_DOWNLOAD_PATH}',
DEFAULT_DOWNLOAD_PATH)
config_io = StringIO(contents)
config = ConfigObj(config_io, unrepr=True)
settings = {'setting_groups': []}
setting_items = (config['setting_groups'].items() +
[('export_settings', config['export_settings'])])
for setting_group, setting_group_dict in setting_items:
settings[setting_group] = {}
for setting_name, setting_dict in setting_group_dict.items():
for key in setting_dict.keys():
if '_callback' in key:
setting_dict[key] = getattr(self, setting_dict[key])
for key, val in setting_dict.items():
setting_dict[key] = val
setting_obj = Setting(name=setting_name, **setting_dict)
settings[setting_group][setting_name] = setting_obj
sgroup_items = config['setting_groups'].items()
for setting_group, setting_group_dict in sgroup_items:
settings['setting_groups'].append(settings[setting_group])
config.pop('setting_groups')
config.pop('export_settings')
for key, val in config.items():
settings[key] = val
return settings
def project_dir(self):
return self._project_dir
def output_dir(self):
return self._output_dir
def project_name(self):
return os.path.basename(os.path.dirname(self.project_dir()))
def get_setting(self, name):
for setting_group in (self.settings['setting_groups'] +
[self.settings['export_settings']]):
if name in setting_group:
setting = setting_group[name]
return setting
def show_error(self, error):
print error
def enable_ui_after_error(self):
pass
def get_versions(self):
response = urllib2.urlopen(self.settings['version_info']['url'])
html = response.read()
nw_version = self.get_setting('nw_version')
old_versions = set(nw_version.values)
new_versions = set(re.findall('(\S+) / \S+', html))
versions = sorted(list(old_versions.union(new_versions)),
key=LooseVersion, reverse=True)
nw_version.values = versions
f = None
try:
f = open(os.path.join(CWD, 'files', 'nw-versions.txt'), 'w')
for v in nw_version.values:
f.write(v+os.linesep)
except IOError:
error = ''.join(traceback.format_exception(sys.exc_info()[0],
sys.exc_info()[1],
sys.exc_info()[2]))
self.show_error(error)
self.enable_ui_after_error()
finally:
if f:
f.close()
def download_file_with_error_handling(self):
setting = self.files_to_download.pop()
location = self.get_setting('download_dir').value
version = self.selected_version()
try:
return self.download_file(setting.url.format(version, version),
setting)
except (Exception, KeyboardInterrupt):
if os.path.exists(setting.save_file_path(version, location)):
os.remove(setting.save_file_path(version, location))
error = ''.join(traceback.format_exception(sys.exc_info()[0],
sys.exc_info()[1],
sys.exc_info()[2]))
self.show_error(error)
self.enable_ui_after_error()
def load_package_json(self, json_path=None):
if json_path is not None:
p_json = [json_path]
else:
p_json = glob.glob(os.path.join(self.project_dir(),
'package.json'))
setting_list = []
if p_json:
json_str = ''
with open(p_json[0], 'r') as f:
json_str = f.read()
try:
setting_list = self.load_from_json(json_str)
except ValueError as e: # Json file is invalid
log('Warning: Json file invalid.')
self.progress_text = '{}\n'.format(e)
return setting_list
def generate_json(self):
if 'webkit' not in self.original_packagejson:
self.original_packagejson['webkit'] = {}
if 'window' not in self.original_packagejson:
self.original_packagejson['window'] = {}
if 'webexe_settings' not in self.original_packagejson:
self.original_packagejson['webexe_settings'] = {}
dic = self.original_packagejson
for setting_name, setting in self.settings['app_settings'].items():
if setting.value is not None:
dic[setting_name] = setting.value
if setting_name == 'keywords':
dic[setting_name] = re.findall("\w+", setting.value)
for setting_name, setting in self.settings['window_settings'].items():
if setting.value is not None:
if 'height' in setting.name or 'width' in setting.name:
try:
dic['window'][setting_name] = int(setting.value)
except ValueError:
pass
else:
dic['window'][setting_name] = setting.value
for setting_name, setting in self.settings['webkit_settings'].items():
if setting.value is not None:
dic['webkit'][setting_name] = setting.value
dl_export_items = (self.settings['download_settings'].items() +
self.settings['export_settings'].items())
for setting_name, setting in dl_export_items:
if setting.value is not None:
dic['webexe_settings'][setting_name] = setting.value
s = json.dumps(dic, indent=4)
return s
@property
def extract_error(self):
return self._extract_error
@extract_error.setter
def extract_error(self, value):
if value is not None and not self.quiet:
self._extract_error = str(value)
sys.stderr.write('\r{}'.format(self._extract_error))
sys.stderr.flush()
@property
def output_err(self):
return self._output_err
@output_err.setter
def output_err(self, value):
if value is not None and not self.quiet:
self._output_err = str(value)
sys.stderr.write('\r{}'.format(self._output_err))
sys.stderr.flush()
@property
def progress_text(self):
return self._progress_text
@progress_text.setter
def progress_text(self, value):
if value is not None and not self.quiet:
self._progress_text = str(value)
sys.stdout.write('\r{}'.format(self._progress_text))
sys.stdout.flush()
def load_from_json(self, json_str):
dic = json.loads(json_str)
self.original_packagejson = dic
setting_list = []
stack = [('root', dic)]
while stack:
parent, new_dic = stack.pop()
for item in new_dic:
setting = self.get_setting(item)
if setting:
setting_list.append(setting)
if (setting.type == 'file' or
setting.type == 'string' or
setting.type == 'folder'):
val_str = self.convert_val_to_str(new_dic[item])
setting.value = val_str
if setting.type == 'check':
setting.value = new_dic[item]
if setting.type == 'list':
val_str = self.convert_val_to_str(new_dic[item])
setting.value = val_str
if isinstance(new_dic[item], dict):
stack.append((item, new_dic[item]))
return setting_list
def selected_version(self):
return self.get_setting('nw_version').value
def extract_files(self):
self.extract_error = None
location = self.get_setting('download_dir').value
version = self.selected_version()
for setting_name, setting in self.settings['export_settings'].items():
save_file_path = setting.save_file_path(version,
location)
try:
if setting.value:
extract_path = os.path.join('files', setting.name)
if os.path.exists(save_file_path):
setting_fbytes = setting.get_file_bytes(version)
for dest_file, fbytes in setting_fbytes:
with open(os.path.join(extract_path,
dest_file), 'wb+') as d:
d.write(fbytes)
self.progress_text += '.'
self.progress_text += '.'
except (tarfile.ReadError, zipfile.BadZipfile) as e:
if os.path.exists(save_file_path):
os.remove(save_file_path)
self.extract_error = e
# cannot use GUI in thread to notify user. Save it for later
self.progress_text = '\nDone.\n'
return True
def create_icns_for_app(self, icns_path):
icon_setting = self.get_setting('icon')
mac_app_icon_setting = self.get_setting('mac_icon')
icon_path = (mac_app_icon_setting.value
if mac_app_icon_setting.value
else icon_setting.value)
if icon_path:
icon_path = os.path.join(self.project_dir(), icon_path)
if not icon_path.endswith('.icns'):
save_icns(icon_path, icns_path)
else:
shutil.copy(icon_path, icns_path)
def replace_icon_in_exe(self, exe_path):
icon_setting = self.get_setting('icon')
exe_icon_setting = self.get_setting('exe_icon')
icon_path = (exe_icon_setting.value
if exe_icon_setting.value
else icon_setting.value)
if icon_path:
p = PEFile(exe_path)
p.replace_icon(os.path.join(self.project_dir(), icon_path))
p.write(exe_path)
p = None
def make_output_dirs(self):
self.output_err = ''
try:
self.progress_text = 'Removing old output directory...\n'
output_dir = os.path.join(self.output_dir(), self.project_name())
temp_dir = os.path.join(TEMP_DIR, 'webexectemp')
if os.path.exists(temp_dir):
shutil.rmtree(temp_dir)
self.progress_text = 'Making new directories...\n'
if not os.path.exists(output_dir):
os.makedirs(output_dir)
os.makedirs(temp_dir)
self.copy_files_to_project_folder()
json_file = os.path.join(self.project_dir(), 'package.json')
if self.output_package_json:
with open(json_file, 'w+') as f:
f.write(self.generate_json())
zip_file = os.path.join(temp_dir, self.project_name()+'.nw')
zip_files(zip_file, self.project_dir(), exclude_paths=[output_dir])
for ex_setting in self.settings['export_settings'].values():
if ex_setting.value:
self.progress_text = '\n'
name = ex_setting.display_name
self.progress_text = 'Making files for {}...'.format(name)
export_dest = os.path.join(output_dir, ex_setting.name)
if os.path.exists(export_dest):
shutil.rmtree(export_dest)
# shutil will make the directory for us
shutil.copytree(os.path.join('files', ex_setting.name),
export_dest)
self.progress_text += '.'
if ex_setting.name == 'mac':
app_path = os.path.join(export_dest,
self.project_name()+'.app')
shutil.move(os.path.join(export_dest,
'node-webkit.app'),
app_path)
self.progress_text += '.'
shutil.copy(zip_file, os.path.join(app_path,
'Contents',
'Resources',
'app.nw'))
self.create_icns_for_app(os.path.join(app_path,
'Contents',
'Resources',
'nw.icns'))
self.progress_text += '.'
else:
ext = ''
windows = False
if ex_setting.name == 'windows':
ext = '.exe'
windows = True
nw_path = os.path.join(export_dest,
ex_setting.dest_files[0])
dest_binary_path = os.path.join(export_dest,
self.project_name() +
ext)
join_files(dest_binary_path, nw_path, zip_file)
sevenfivefive = (stat.S_IRWXU |
stat.S_IRGRP |
stat.S_IXGRP |
stat.S_IROTH |
stat.S_IXOTH)
os.chmod(dest_binary_path, sevenfivefive)
if windows:
self.replace_icon_in_exe(dest_binary_path)
self.progress_text += '.'
if os.path.exists(nw_path):
os.remove(nw_path)
except Exception:
exc = traceback.format_exception(sys.exc_info()[0],
sys.exc_info()[1],
sys.exc_info()[2])
self.output_err += ''.join(exc)
finally:
shutil.rmtree(temp_dir)
def copy_files_to_project_folder(self):
old_dir = CWD
os.chdir(self.project_dir())
for sgroup in self.settings['setting_groups']:
for setting in sgroup.values():
if setting.type == 'file' and setting.value:
f_path = setting.value.replace(self.project_dir(), '')
if not os.path.exists(f_path):
try:
shutil.copy(setting.value, self.project_dir())
setting.value = os.path.basename(setting.value)
except shutil.Error as e: # same file warning
log('Warning: {}'.format(e))
os.chdir(old_dir)
def convert_val_to_str(self, val):
if isinstance(val, (list, tuple)):
return ', '.join(val)
return str(val).replace(self.project_dir()+os.path.sep, '')
def export(self):
self.get_files_to_download()
res = self.try_to_download_files()
if res:
self.make_output_dirs()
self.progress_text = '\nDone!\n'
self.delete_files()
def get_files_to_download(self):
self.files_to_download = []
for setting_name, setting in self.settings['export_settings'].items():
if setting.value is True:
self.files_to_download.append(setting)
return True
def try_to_download_files(self):
if self.files_to_download:
return self.download_file_with_error_handling()
def continue_downloading_or_extract(self):
if self.files_to_download:
return self.download_file_with_error_handling()
else:
self.progress_text = 'Extracting files.'
return self.extract_files()
def download_file(self, path, setting):
location = self.get_setting('download_dir').value
url = path
file_name = setting.save_file_path(self.selected_version(), location)
archive_exists = os.path.exists(file_name)
dest_files_exist = False
forced = self.get_setting('force_download').value
if (archive_exists or dest_files_exist) and not forced:
return self.continue_downloading_or_extract()
web_file = urllib2.urlopen(url)
f = open(file_name, 'wb')
meta = web_file.info()
file_size = int(meta.getheaders("Content-Length")[0])
version = self.selected_version()
version_file = self.settings['base_url'].format(version)
short_name = path.replace(version_file, '')
MB = file_size/1000000.0
self.progress_text = ('Downloading: {}, '
'Size: {:.2f} MB\n'.format(short_name,
MB))
file_size_dl = 0
block_sz = 8192
while True:
buff = web_file.read(block_sz)
if not buff:
break
file_size_dl += len(buff)
DL_MB = file_size_dl/1000000.0
percent = file_size_dl*100.0/file_size
f.write(buff)
args = (DL_MB, MB, percent)
status = "{:10.2f}/{:.2f} MB [{:3.2f}%]".format(*args)
self.progress_text = status
self.progress_text = '\nDone downloading.\n'
f.close()
return self.continue_downloading_or_extract()
def delete_files(self):
for ex_setting in self.settings['export_settings'].values():
for dest_file in ex_setting.dest_files:
f_path = os.path.join('files', ex_setting.name, dest_file)
if os.path.exists(f_path):
os.remove(f_path)
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description=('Command line interface '
'to web2exe'),
prog='web2execmd')
command_base = CommandBase()
parser.add_argument('project_dir', metavar='project_dir',
help='The project directory.')
parser.add_argument('--output-dir', dest='output_dir',
help='The output directory for exports.')
parser.add_argument('--quiet', dest='quiet', action='store_true',
default=False,
help='Silences output messages')
parser.add_argument('--package-json',
dest='load_json',
nargs='?',
default='',
const=True,
help=('Loads the package.json '
'file in the project directory. '
'Ignores other command line arguments.'))
for setting_group_dict in command_base.settings['setting_groups']:
for setting_name, setting in setting_group_dict.items():
kwargs = {}
if setting_name == 'name':
kwargs.update({'default': command_base.project_name})
else:
kwargs.update({'required': setting.required,
'default': setting.default_value})
action = 'store'
option_name = setting_name.replace('_', '-')
if isinstance(setting.default_value, bool):
action = ('store_true' if setting.default_value is False
else 'store_false')
kwargs.update({'action': action})
if setting.default_value is True:
option_name = 'disable-{}'.format(option_name)
else:
kwargs.update({'choices': setting.values or None,
'metavar': '<{}>'.format(setting.display_name)})
parser.add_argument('--{}'.format(option_name),
dest=setting_name,
help=setting.description,
**kwargs
)
export_args = [arg for arg in command_base.settings['export_settings']]
parser.add_argument('--export-to', dest='export_options',
nargs='+', required=True,
choices=export_args,
help=('Choose at least one system '
'to export to.'))
args = parser.parse_args()
if args.quiet:
command_base.quiet = True
command_base._project_dir = args.project_dir
command_base._output_dir = (args.output_dir or
os.path.join(args.project_dir, 'output'))
if not args.title:
args.title = command_base.project_name()
for name, val in args._get_kwargs():
if callable(val):
val = val()
if name == 'export_options':
for opt in val:
setting = command_base.get_setting(opt)
if setting is not None:
setting.value = True
else:
setting = command_base.get_setting(name)
if setting is not None:
setting.value = val
if args.load_json is True:
command_base.load_package_json()
elif args.load_json:
command_base.load_package_json(args.load_json)
command_base.export()

View file

@ -1,3 +1,6 @@
0.11.3
0.11.2
0.11.1
0.11.0-rc1
0.11.0
0.10.5

220
files/settings.cfg Normal file
View file

@ -0,0 +1,220 @@
base_url='http://dl.node-webkit.org/v{}/'
win_32_dir_prefix = 'node-webkit-v{}-win-ia32'
mac_32_dir_prefix = 'node-webkit-v{}-osx-ia32'
linux_32_dir_prefix = 'node-webkit-v{}-linux-ia32'
win_64_dir_prefix = 'node-webkit-v{}-win-x64'
mac_64_dir_prefix = 'node-webkit-v{}-osx-x64'
linux_64_dir_prefix = 'node-webkit-v{}-linux-x64'
[setting_groups]
[[app_settings]]
[[[main]]]
display_name='Main html file'
required=True
type='file'
file_types='*.html *.php *.htm'
description='Main html file relative to the project directory.'
[[[name]]]
display_name='App Name'
required=True
type='string'
[[[description]]]
default_value=''
type='string'
[[[version]]]
default_value='0.1.0'
type='string'
[[[keywords]]]
default_value=''
type='string'
[[[nodejs]]]
display_name='Include Nodejs'
default_value=True
type='check'
[[[node-main]]]
display_name='Alt. Nodejs'
default_value=''
type='file'
file_types='*.js'
[[[single-instance]]]
display_name='Single Instance'
default_value=True
type='check'
description='Restrict the app to run with only a single instance allowed at a time.'
[[webkit_settings]]
[[[plugin]]]
display_name='Load Plugins'
default_value=False
type='check'
[[[java]]]
display_name='Load Java'
default_value=False
type='check'
[[[page-cache]]]
display_name='Page Cache'
default_value=False
type='check'
[[window_settings]]
[[[title]]]
default_value=''
type='string'
[[[icon]]]
display_name='Window Icon'
default_value=''
type='file'
file_types='*.png *.jpg *.jpeg'
[[[mac_icon]]]
default_value=''
type='file'
file_types='*.png *.jpg *.jpeg *.icns'
description='This icon to be displayed for the Mac Application. Defaults to Window Icon'
[[[exe_icon]]]
default_value=''
type='file'
file_types='*.png *.jpg *.jpeg'
description='This icon to be displayed for the windows exe of the app. Defaults to Winodow icon.'
[[[width]]]
default_value='640'
type='string'
[[[height]]]
default_value='480'
type='string'
[[[min_width]]]
default_value=None
type='string'
[[[min_height]]]
default_value=None
type='string'
[[[max_width]]]
default_value=None
type='string'
[[[max_height]]]
default_value=None
type='string'
[[[toolbar]]]
display_name='Show Toolbar'
default_value=False
type='check'
description=''
[[[always-on-top]]]
display_name='Keep on Top'
default_value=False
type='check'
description=''
[[[frame]]]
display_name='Window Frame'
default_value=True
type='check'
description='Hide the frame of the window'
[[[show_in_taskbar]]]
display_name='Taskbar'
default_value=True
type='check'
description='Hide the app running in the taskbar'
[[[visible]]]
default_value=True
type='check'
description=''
[[[resizable]]]
default_value=False
type='check'
description=''
[[[fullscreen]]]
default_value=False
type='check'
description=''
[[[position]]]
display_name='Position by'
default_value=None
values=[None, 'mouse', 'center']
type='list'
description='The position to place the window when it opens.'
[[[as_desktop]]]
default_value=False
type='check'
description='Tries to render the app to the desktop background'
[[[transparent]]]
default_value=False
type='check'
description='Allows window tranparency.'
[[download_settings]]
[[[nw_version]]]
display_name='Node-webkit version'
default_value='0.9.2'
values=[]
type='list'
button='Update'
button_callback='update_nw_versions'
[[[force_download]]]
default_value=False
type='check'
[[[download_dir]]]
display_name='Download location'
default_value='{DEFAULT_DOWNLOAD_PATH}'
type='folder'
[export_settings]
[[windows]]
default_value=False
type='check'
url='%(base_url)s%(win_32_dir_prefix)s.zip'
extract_files="""['%(win_32_dir_prefix)s/nw.exe',
'%(win_32_dir_prefix)s/icudtl.dat',
'%(win_32_dir_prefix)s/libEGL.dll',
'%(win_32_dir_prefix)s/libGLESv2.dll']"""
dest_files="""['nw.exe',
'nw.pak',
'icudtl.dat',
'libEGL.dll',
'libGLESv2.dll']"""
[[mac]]
default_value=False
type='check'
url='%(base_url)s%(mac_32_dir_prefix)s.zip'
extract_file='%(mac_32_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/node-webkit Framework'
extract_files="""['%(mac_32_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/node-webkit Framework',
'%(mac_32_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/nw.pak',
'%s(mac_32_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/icudtl.dat']"""
dest_files="""['node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/node-webkit Framework',
'node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/nw.pak',
'node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/icudtl.dat']"""
[[linux-x64]]
default_value=False
type='check'
url='%(base_url)s%(linux_64_dir_prefix)s.tar.gz'
extract_file='%(linux_64_dir_prefix)s/nw'
extract_files="""['%(linux_64_dir_prefix)s/nw',
'%(linux_64_dir_prefix)s/nw.pak',
'%(linux_64_dir_prefix)s/icudtl.dat']"""
dest_files=['nw', 'nw.pak', 'icudtl.dat']
[[linux-x32]]
default_value=False
type='check'
url='%(base_url)s%(linux_32_dir_prefix)s.tar.gz'
extract_file='%(linux_32_dir_prefix)s/nw'
extract_files="""['%(linux_32_dir_prefix)s/nw',
'%(linux_32_dir_prefix)s/nw.pak',
'%(linux_32_dir_prefix)s/icudtl.dat']"""
dest_files=['nw', 'nw.pak', 'icudtl.dat']
[order]
application_setting_order="""['main', 'name', 'node-main', 'description', 'version', 'keywords',
'nodejs', 'single-instance', 'plugin',
'java', 'page-cache']"""
window_setting_order = """['title', 'icon', 'mac_icon', 'exe_icon', 'position', 'width', 'height',
'min_width', 'min_height',
'max_width', 'max_height', 'toolbar', 'always-on-top', 'frame',
'show_in_taskbar', 'visible', 'resizable', 'fullscreen', 'as_desktop',
'transparent']"""
export_setting_order = """['windows', 'linux-x64', 'mac', 'linux-x32']"""
download_setting_order = """['nw_version', 'download_dir','force_download']"""
[version_info]
url='https://raw.githubusercontent.com/rogerwang/node-webkit/master/CHANGELOG.md'

View file

@ -1,5 +1,5 @@
import struct
from image_utils import Image, nearest_icon_size, resize
import image_utils
import png
@ -638,7 +638,10 @@ class ICNSHeader(Structure):
self.elements = []
def parse_image(self, image):
icon_size = nearest_icon_size(image.size[0], image.size[1])
if not image_utils.IMAGE_UTILS_AVAILABLE:
return None
icon_size = image_utils.nearest_icon_size(image.size[0], image.size[1])
icon_sizes = [icon_size]
@ -649,7 +652,7 @@ class ICNSHeader(Structure):
for icon_s in icon_sizes:
icns_element = ICNSElement()
im_data = resize(image, (icon_s, icon_s))
im_data = image_utils.resize(image, (icon_s, icon_s))
png_file = png.Reader(bytes=im_data)

View file

@ -1,6 +1,7 @@
from cStringIO import StringIO
try:
from PIL import Image as im
IMAGE_UTILS_AVAILABLE = True
Image = im.open
def resize(image, size):
output = StringIO()
@ -17,7 +18,7 @@ try:
output.close()
return contents
except ImportError:
raise Exception('Python image library PIL/pillow is required.')
IMAGE_UTILS_AVAILABLE = False
LARGEST_ICON_SIZE = 1024
SMALLEST_ICON_SIZE = 16

1103
main.py

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
from icns_info import *
from image_utils import Image, nearest_icon_size, resize
from image_utils import Image
import sys
"""This module takes any image that is readable by PIL and exports it to an icns file.
@ -15,8 +15,9 @@ def encode_image_to_icns(image_path):
def save_icns(image_path, icns_path):
im_data = encode_image_to_icns(image_path)
icns_path = icns_path if icns_path.endswith('.icns') else icns_path+'.icns'
open(icns_path, 'wb+').write(im_data)
if im_data is not None:
icns_path = icns_path if icns_path.endswith('.icns') else icns_path+'.icns'
open(icns_path, 'wb+').write(im_data)
if __name__ == '__main__':
if len(sys.argv) != 3:

3
requirements.txt Normal file
View file

@ -0,0 +1,3 @@
pillow
pyside
configobj

View file

@ -3,6 +3,12 @@ import os, zipfile, io, platform
import sys, tempfile
import subprocess
try:
import zlib
ZIP_MODE = zipfile.ZIP_DEFLATED
except:
ZIP_MODE = zipfile.ZIP_STORED
DEBUG = False
def is_windows():
@ -15,7 +21,6 @@ def log(*args):
if DEBUG:
print(*args)
def open_folder_in_explorer(path):
if platform.system() == "Windows":
os.startfile(path)
@ -25,7 +30,7 @@ def open_folder_in_explorer(path):
subprocess.Popen(["xdg-open", path])
def zip_files(zip_file_name, *args, **kwargs):
zip_file = zipfile.ZipFile(zip_file_name, 'w')
zip_file = zipfile.ZipFile(zip_file_name, 'w', ZIP_MODE)
verbose = kwargs.pop('verbose', False)
exclude_paths = kwargs.pop('exclude_paths', [])
old_path = os.getcwd()
@ -55,7 +60,7 @@ def zip_files(zip_file_name, *args, **kwargs):
else:
file = os.path.abspath(arg)
directory = os.path.abspath(os.path.join(file,'..'))
directory = os.path.abspath(os.path.join(file, '..'))
os.chdir(directory)
file_loc = os.path.relpath(arg, directory)
if verbose:
@ -76,29 +81,3 @@ def join_files(destination, *args, **kwargs):
if len(bytes) == 0:
break
dest_file.write(bytes)
#def convert_icns_to_png(inputname, outputname, size=0, out_type='png'):
# try:
# # new_from_file_at_size() does not work, requires incremental loader
# pixbuf = GdkPixbuf.Pixbuf.new_from_file(inputname)
# if size:
# width, height = pixbuf.get_width(), pixbuf.get_height()
# if width > height:
# if width > size:
# height = height * size / width
# width = size
# else:
# if height > size:
# width = width * size / height
# height = size
#
# scaled = GdkPixbuf.Pixbuf.scale_simple(pixbuf, width, height,
# GdkPixbuf.InterpType.BILINEAR)
# else:
# scaled = pixbuf
#
# scaled.savev(outputname, out_type, [], [])
#
# except GLib.GError as e:
# sys.stderr.write("%s:%d: %s\n" % (e.domain, e.code, e))
# return e.code