Skip to content

Commit 9fc11c1

Browse files
committed
Fix #150: Add support for naming patterns
1 parent fadea9a commit 9fc11c1

File tree

4 files changed

+223
-30
lines changed

4 files changed

+223
-30
lines changed

command_line.py

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,83 @@ def project_name(self):
144144
return (self._project_name or
145145
os.path.basename(os.path.abspath(self.project_dir())))
146146

147+
def sub_output_pattern(self, pattern):
148+
"""
149+
Substitute patterns for setting values
150+
"""
151+
byte_pattern = bytearray(pattern.encode())
152+
153+
val_dict = self.get_tag_value_dict()
154+
155+
start = 0
156+
end = 0
157+
158+
in_sub = False
159+
160+
i = 0
161+
162+
while i < len(byte_pattern):
163+
char = chr(byte_pattern[i])
164+
next_char = None
165+
if i != len(byte_pattern) - 1:
166+
next_char = chr(byte_pattern[i+1])
167+
168+
if char == '%':
169+
if next_char == '(':
170+
start = i
171+
in_sub = True
172+
173+
if in_sub:
174+
end = i
175+
176+
if char == ')':
177+
in_sub = False
178+
old_string = str(byte_pattern[start:end+1], 'utf-8')
179+
sub = val_dict.get(old_string)
180+
181+
if sub is not None:
182+
sub = str(sub)
183+
byte_pattern[start:end+1] = sub.encode()
184+
i = i + (len(sub)-len(old_string))
185+
186+
i += 1
187+
188+
return str(byte_pattern, 'utf-8').replace('/', '_').replace('\\', '_')
189+
190+
191+
def get_tag_dict(self):
192+
"""
193+
Gets the tag dictionary used to populate the
194+
auto completion of the output name pattern field
195+
196+
Returns:
197+
A dict object containing the mapping between friendly names
198+
and setting names.
199+
"""
200+
tag_dict = {}
201+
for setting_group in (self.settings['setting_groups'] +
202+
[self.settings['export_settings']] +
203+
[self.settings['compression']]):
204+
for key in setting_group.keys():
205+
setting = setting_group[key]
206+
tag_dict[setting.display_name] = '%('+key+')'
207+
208+
return tag_dict
209+
210+
def get_tag_value_dict(self):
211+
"""
212+
Gets the tag to value dictionary to substitute values
213+
"""
214+
tag_dict = {}
215+
for setting_group in (self.settings['setting_groups'] +
216+
[self.settings['export_settings']] +
217+
[self.settings['compression']]):
218+
for key in setting_group.keys():
219+
setting = setting_group[key]
220+
tag_dict['%('+key+')'] = setting.value
221+
222+
return tag_dict
223+
147224
def get_setting(self, name):
148225
"""Get a setting by name
149226
@@ -153,19 +230,13 @@ def get_setting(self, name):
153230
Returns:
154231
A setting object or None
155232
"""
156-
# Check for alternate names in the settings
157-
# due to nw.js changing some names in newer versions
158-
name_no_underscores = name.replace('_', '-')
159233

160234
for setting_group in (self.settings['setting_groups'] +
161235
[self.settings['export_settings']] +
162236
[self.settings['compression']]):
163237
if name in setting_group:
164238
setting = setting_group[name]
165239
return setting
166-
elif name_no_underscores in setting_group:
167-
setting = setting_group[name_no_underscores]
168-
return setting
169240

170241
def get_settings_type(self, type):
171242
"""Get all settings with a specific type"""
@@ -749,7 +820,10 @@ def process_export_setting(self, ex_setting, output_dir,
749820

750821
def make_output_dirs(self, write_json=True):
751822
"""Create the output directories for the application to be copied"""
752-
output_dir = utils.path_join(self.output_dir(), self.project_name())
823+
824+
output_name = self.sub_pattern() or self.project_name()
825+
826+
output_dir = utils.path_join(self.output_dir(), output_name)
753827
temp_dir = utils.path_join(config.TEMP_DIR, 'webexectemp')
754828

755829
self.progress_text = 'Making new directories...\n'
@@ -770,6 +844,10 @@ def make_output_dirs(self, write_json=True):
770844
self.process_export_setting(ex_setting, output_dir, temp_dir,
771845
app_loc, uncompressed)
772846

847+
def sub_pattern(self):
848+
"""Returns the output pattern substitution or an empty string"""
849+
setting = self.get_setting('output_pattern')
850+
return self.sub_output_pattern(setting.value)
773851

774852
def try_make_output_dirs(self):
775853
"""Try to create the output directories if they don't exist"""

files/settings.cfg

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,21 @@ linux_64_dir_prefix = 'nwjs-v{}-linux-x64'
113113

114114
[[web2exe_settings]]
115115
[[[export_dir]]]
116+
display_name='Output Directory'
116117
default_value=''
117118
type='string'
119+
description='The output directory relative to the project directory.'
118120
[[[custom_script]]]
121+
display_name='Execute Script'
119122
default_value=''
120123
copy=False
121124
type='file'
125+
description='The script to execute after a project was successfully exported.'
126+
[[[output_pattern]]]
127+
display_name='Output Name Pattern'
128+
default_value=''
129+
type='string'
130+
description='Instead of using the App Name for the output directory, this field can\nbe used to reference different settings of the package.json to name the output folder.'
122131

123132
[[window_settings]]
124133
[[[id]]]
@@ -228,8 +237,8 @@ linux_64_dir_prefix = 'nwjs-v{}-linux-x64'
228237

229238
[[download_settings]]
230239
[[[nw_version]]]
231-
display_name='Node-webkit version'
232-
default_value='0.12.0'
240+
display_name='NW.js version'
241+
default_value='0.16.0'
233242
values=[]
234243
type='list'
235244
button='Update'

main.py

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import glob
3+
import re
34
import sys
45
import codecs
56
import platform
@@ -13,11 +14,12 @@
1314

1415
from util_classes import ExistingProjectDialog
1516
from util_classes import BackgroundThread, Validator
17+
from util_classes import CompleterLineEdit, TagsCompleter
1618

1719
from PySide import QtGui, QtCore
1820
from PySide.QtGui import (QApplication, QHBoxLayout, QVBoxLayout)
1921
from PySide.QtNetwork import QHttp
20-
from PySide.QtCore import QUrl, QFile, QIODevice, QCoreApplication
22+
from PySide.QtCore import Qt, QUrl, QFile, QIODevice, QCoreApplication
2123

2224
from image_utils.pycns import pngs_from_icns
2325

@@ -46,6 +48,7 @@ def __init__(self, width, height, app, parent=None):
4648

4749
self.script_line = None
4850
self.output_line = None
51+
self.output_name_line = None
4952

5053
self.download_bar_widget = None
5154
self.app_settings_widget = None
@@ -814,6 +817,12 @@ def load_project(self, directory):
814817
script_setting = self.get_setting('custom_script')
815818
self.script_line.setText(script_setting.value)
816819

820+
output_name_setting = self.get_setting('output_pattern')
821+
self.output_name_line.setText(output_name_setting.value)
822+
823+
self.output_name_line.textChanged.connect(self.output_name_line.text_changed)
824+
self.output_name_line.textChanged.connect(self.completer.update)
825+
817826
self.set_window_icon()
818827
self.open_export_button.setEnabled(True)
819828
self.update_json = True
@@ -906,25 +915,62 @@ def create_setting(self, name):
906915

907916
def create_window_settings(self):
908917
group_box = QtGui.QWidget()
909-
vlayout = self.create_layout(self.settings['order']['window_setting_order'], cols=3)
918+
win_setting_order = self.settings['order']['window_setting_order']
919+
vlayout = self.create_layout(win_setting_order, cols=3)
910920

911921
group_box.setLayout(vlayout)
912922
return group_box
913923

914924
def create_export_settings(self):
915925
group_box = QtGui.QWidget()
916-
vlayout = self.create_layout(self.settings['order']['export_setting_order'], cols=4)
926+
927+
ex_setting_order = self.settings['order']['export_setting_order']
928+
929+
vlayout = self.create_layout(ex_setting_order, cols=4)
930+
931+
932+
output_name_layout = QtGui.QHBoxLayout()
933+
934+
output_name_setting = self.get_setting('output_pattern')
935+
output_name_label = QtGui.QLabel(output_name_setting.display_name+':')
936+
output_name_label.setMinimumWidth(155)
937+
938+
tag_dict = self.get_tag_dict()
939+
self.output_name_line = CompleterLineEdit(tag_dict)
940+
941+
completer = TagsCompleter(self.output_name_line, tag_dict)
942+
completer.setCaseSensitivity(Qt.CaseInsensitive)
943+
944+
completer.activated.connect(self.output_name_line.complete_text)
945+
self.completer = completer
946+
self.completer.setWidget(self.output_name_line)
947+
948+
self.output_name_line.textChanged.connect(
949+
self.call_with_object('setting_changed',
950+
self.output_name_line,
951+
output_name_setting)
952+
)
953+
954+
self.output_name_line.setStatusTip(output_name_setting.description)
955+
956+
output_name_layout.addWidget(output_name_label)
957+
output_name_layout.addWidget(self.output_name_line)
917958

918959
output_layout = QtGui.QHBoxLayout()
919960

920-
output_label = QtGui.QLabel('Output Directory:')
921-
output_label.setMinimumWidth(150)
961+
ex_dir_setting = self.get_setting('export_dir')
962+
output_label = QtGui.QLabel(ex_dir_setting.display_name+':')
963+
output_label.setMinimumWidth(155)
922964
self.output_line = QtGui.QLineEdit()
923-
self.output_line.textChanged.connect(self.call_with_object('setting_changed',
924-
self.output_line,
925-
self.get_setting('export_dir')))
965+
966+
self.output_line.textChanged.connect(
967+
self.call_with_object('setting_changed',
968+
self.output_line,
969+
ex_dir_setting)
970+
)
971+
926972
self.output_line.textChanged.connect(self.project_path_changed)
927-
self.output_line.setStatusTip('The output directory relative to the project directory.')
973+
self.output_line.setStatusTip(ex_dir_setting.description)
928974
output_button = QtGui.QPushButton('...')
929975
output_button.clicked.connect(self.browse_out_dir)
930976

@@ -934,19 +980,20 @@ def create_export_settings(self):
934980

935981
script_layout = QtGui.QHBoxLayout()
936982

937-
script_label = QtGui.QLabel('Execute Script:')
938-
script_label.setMinimumWidth(150)
983+
script_setting = self.get_setting('custom_script')
984+
script_label = QtGui.QLabel(script_setting.display_name+':')
985+
script_label.setMinimumWidth(155)
939986

940987
self.script_line = QtGui.QLineEdit()
941988

942-
script_setting = self.get_setting('custom_script')
943989
self.script_line.setObjectName(script_setting.name)
944990

945-
self.script_line.textChanged.connect(self.call_with_object('setting_changed',
946-
self.script_line,
947-
script_setting))
948-
self.script_line.setStatusTip('The script to execute after a '
949-
'project was successfully exported.')
991+
self.script_line.textChanged.connect(
992+
self.call_with_object('setting_changed',
993+
self.script_line,
994+
script_setting)
995+
)
996+
self.script_line.setStatusTip(script_setting.description)
950997
script_button = QtGui.QPushButton('...')
951998

952999
file_types = ['*.py']
@@ -956,15 +1003,20 @@ def create_export_settings(self):
9561003
else:
9571004
file_types.append('*.bash')
9581005

959-
script_button.clicked.connect(self.call_with_object('get_file_reg',
960-
self.script_line, script_setting,
961-
' '.join(file_types)))
1006+
script_button.clicked.connect(
1007+
self.call_with_object('get_file_reg',
1008+
self.script_line,
1009+
script_setting,
1010+
' '.join(file_types))
1011+
)
1012+
9621013
script_layout.addWidget(script_label)
9631014
script_layout.addWidget(self.script_line)
9641015
script_layout.addWidget(script_button)
9651016

9661017
vbox = QtGui.QVBoxLayout()
9671018
vbox.addLayout(vlayout)
1019+
vbox.addLayout(output_name_layout)
9681020
vbox.addLayout(output_layout)
9691021
vbox.addLayout(script_layout)
9701022

@@ -1321,7 +1373,7 @@ def show_and_raise(self):
13211373
self.existing_dialog.raise_()
13221374

13231375

1324-
if __name__ == '__main__':
1376+
def main():
13251377
app = QApplication(sys.argv)
13261378

13271379
QCoreApplication.setApplicationName("Web2Executable")
@@ -1333,3 +1385,6 @@ def show_and_raise(self):
13331385
frame.show_and_raise()
13341386

13351387
sys.exit(app.exec_())
1388+
1389+
if __name__ == '__main__':
1390+
main()

util_classes.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,54 @@ def __repr__(self):
237237
self.required,
238238
self.type,
239239
url)
240+
241+
class CompleterLineEdit(QtGui.QLineEdit):
242+
243+
def __init__(self, tag_dict, *args):
244+
QtGui.QLineEdit.__init__(self, *args)
245+
246+
self.pref = ''
247+
self.tag_dict = tag_dict
248+
249+
def text_changed(self, text):
250+
all_text = str(text)
251+
text = all_text[:self.cursorPosition()]
252+
prefix = re.split('[^%a-zA-Z)(_-]', text)[-1].strip()
253+
self.pref = prefix
254+
if prefix.strip() != prefix:
255+
self.pref = ''
256+
257+
def complete_text(self, text):
258+
cursor_pos = self.cursorPosition()
259+
before_text = str(self.text())[:cursor_pos]
260+
after_text = str(self.text())[cursor_pos:]
261+
prefix_len = len(re.split('[^%a-zA-Z)(_-]', before_text)[-1].strip())
262+
tag_text = self.tag_dict.get(text)
263+
264+
if tag_text is None:
265+
tag_text = text
266+
267+
new_text = '{}{}{}'.format(before_text[:cursor_pos - prefix_len],
268+
tag_text,
269+
after_text)
270+
self.setText(new_text)
271+
self.setCursorPosition(len(new_text))
272+
273+
class TagsCompleter(QtGui.QCompleter):
274+
275+
def __init__(self, parent, all_tags):
276+
self.keys = sorted(all_tags.keys())
277+
self.vals = sorted([val for val in all_tags.values()])
278+
self.tags = list(sorted(self.vals+self.keys))
279+
QtGui.QCompleter.__init__(self, self.tags, parent)
280+
self.editor = parent
281+
282+
def update(self, text):
283+
obj = self.editor
284+
completion_prefix = obj.pref
285+
model = QtGui.QStringListModel(self.tags, self)
286+
self.setModel(model)
287+
288+
self.setCompletionPrefix(completion_prefix)
289+
if completion_prefix.strip() != '':
290+
self.complete()

0 commit comments

Comments
 (0)