Skip to content

Commit d56d2e6

Browse files
committed
📚 copy from zip to zip file. resolves #307. ✨ write multiple files into a zip. fix #308"
1 parent a38e141 commit d56d2e6

File tree

13 files changed

+262
-30
lines changed

13 files changed

+262
-30
lines changed

docs/README.rst

+5-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ This section covers the use cases for moban. Please check them out individually.
2222
#. `Force template type from moban file`_
2323
#. `User defined template types`_
2424
#. `Select a group target to run`_
25-
25+
#. `Template files in a zip or tar`_
26+
#. `Template copying from a zip to a zip`_
27+
2628
.. _Jinja2 command line: level-1-jinja2-cli
2729
.. _Template inheritance: level-2-template-inheritance
2830
.. _Data override: level-3-data-override
@@ -42,3 +44,5 @@ This section covers the use cases for moban. Please check them out individually.
4244
.. _Force template type from moban file: level-17-force-template-type-from-moban-file
4345
.. _User defined template types: level-18-user-defined-template-types
4446
.. _Select a group target to run: level-19-moban-a-sub-group-in-targets
47+
.. _Template files in a zip or tar: level-20-templates-configs-in-zip-or-tar
48+
.. _Template copying from a zip to a zip: level-21-copy-templates-into-an-alien-file-system

docs/index.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ examples folder.
3434
level-17-force-template-type-from-moban-file/README.rst
3535
level-18-user-defined-template-types/README.rst
3636
level-19-moban-a-sub-group-in-targets/README.rst
37-
37+
level-20-templates-configs-in-zip-or-tar/README.rst
38+
level-21-copy-templates-into-an-alien-file-system/README.rst
3839

3940
For more complex use case, please look at `its usage in pyexcel project <http://pyexcel.readthedocs.io/en/latest/guide.html>`_
4041

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
configuration:
2+
template_dir:
3+
- "zip://template-sources.zip"
4+
targets:
5+
- output: "zip://my.zip/simple.file.copy"
6+
template: file-in-template-sources-folder.txt
7+
template_type: copy
8+
- output: "zip://my.zip/target_without_template_type"
9+
template: file_extension_will_trigger.copy
10+
- "zip://my.zip/target_in_short_form": as_long_as_this_one_has.copy
11+
- output: "zip://my.zip/misc-1-copying/can-create-folder/if-not-exists.txt"
12+
template: file-in-template-sources-folder.txt
13+
template_type: copy
14+
- output: "zip://my.zip/test-dir"
15+
template: dir-for-copying
16+
template_type: copy
17+
- output: "zip://my.zip/test-recursive-dir"
18+
template: dir-for-recusive-copying/**
19+
template_type: copy
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
Level 21: template copying from a zip to a zip
2+
================================================================================
3+
4+
In level 15, with `.moban.yml`, you can copy templates to your destination. Now
5+
with similiar moban syntax, let me show how to create a new zip file where
6+
all templates are copied to.
7+
8+
Explicit syntax::
9+
10+
targets:
11+
- output: "zip://your.zip/explicit"
12+
template: template_file
13+
template_type: copy
14+
15+
16+
Implicit syntax::
17+
18+
targets:
19+
- output: "zip://your.zip/implicit"
20+
template: template_file.copy
21+
22+
23+
Shorthand syntax::
24+
25+
targets:
26+
- "zip://your.zip/shorthand": template_file.copy
27+
28+
29+
No implicit nor short hand syntax for the following directory copying unless
30+
you take a look at `force-template-type`. When you read
31+
`level-17-force-template-type-from-moban-file/README.rst`, you will find
32+
out more.
33+
34+
35+
Directory copying syntax::
36+
37+
38+
targets:
39+
- output: "zip://your.zip/dest-dir"
40+
template: source-dir
41+
template_type: copy
42+
43+
44+
Recursive directory copying syntax::
45+
46+
47+
targets:
48+
- output: "zip://your.zip/dest-dir"
49+
template: source-dir/**
50+
template_type: copy
51+
52+
53+
Evaluation
54+
--------------------------------------------------------------------------------
55+
56+
Here is example moban file for copying::
57+
58+
configuration:
59+
template_dir:
60+
- "zip://template-sources.zip"
61+
targets:
62+
- output: "zip://my.zip/simple.file.copy"
63+
template: file-in-template-sources-folder.txt
64+
template_type: copy
65+
- output: "zip://my.zip/target_without_template_type"
66+
template: file_extension_will_trigger.copy
67+
- "zip://my.zip/target_in_short_form": as_long_as_this_one_has.copy
68+
- output: "zip://my.zip/misc-1-copying/can-create-folder/if-not-exists.txt"
69+
template: file-in-template-sources-folder.txt
70+
template_type: copy
71+
- output: "zip://my.zip/test-dir"
72+
template: dir-for-copying
73+
template_type: copy
74+
- output: "zip://my.zip/test-recursive-dir"
75+
template: dir-for-recusive-copying/**
76+
template_type: copy
77+
78+
79+
template copy does:
80+
81+
82+
#. copies any template inside pre-declared template directory to anywhere. moban will create directory if needed.
83+
#. copies any directory to anywhere. If "**" is followed, moban attempts to do recursive copying.
Binary file not shown.
Binary file not shown.

moban/file_system.py

+44-14
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,19 @@ def read_unicode(path):
7777
@log_fs_failure
7878
def read_binary(path):
7979
path = to_unicode(path)
80-
dir_name = fs.path.dirname(path)
81-
the_file_name = fs.path.basename(path)
82-
with fs.open_fs(dir_name) as fs_system:
83-
return fs_system.readbytes(the_file_name)
80+
if "zip://" in path:
81+
zip_file, folder = path.split(".zip/")
82+
with fs.open_fs(zip_file + ".zip") as the_fs:
83+
return the_fs.readbytes(folder)
84+
elif "tar://" in path:
85+
tar_file, folder = path.split(".tar/")
86+
with fs.open_fs(tar_file + ".tar") as the_fs:
87+
return the_fs.readbytes(folder)
88+
else:
89+
dir_name = fs.path.dirname(path)
90+
the_file_name = fs.path.basename(path)
91+
with fs.open_fs(dir_name) as fs_system:
92+
return fs_system.readbytes(the_file_name)
8493

8594

8695
@log_fs_failure
@@ -133,18 +142,39 @@ def is_file(path):
133142
@log_fs_failure
134143
def exists(path):
135144
path = to_unicode(path)
145+
136146
if "zip://" in path:
137-
try:
138-
with fs.open_fs(path) as the_fs:
139-
return True
140-
except fs.errors.CreateFailed:
141-
return False
147+
if path.endswith(".zip"):
148+
zip_file, folder = path, "/"
149+
try:
150+
with fs.open_fs(zip_file) as the_fs:
151+
return True
152+
except fs.errors.CreateFailed:
153+
return False
154+
else:
155+
zip_file, folder = path.split(".zip/")
156+
try:
157+
with fs.open_fs(zip_file + ".zip") as the_fs:
158+
return the_fs.exists(folder)
159+
except fs.errors.CreateFailed:
160+
return False
161+
142162
if "tar://" in path:
143-
try:
144-
with fs.open_fs(path) as the_fs:
145-
return True
146-
except fs.errors.CreateFailed:
147-
return False
163+
if path.endswith(".tar"):
164+
zip_file, folder = path, "/"
165+
try:
166+
with fs.open_fs(zip_file) as the_fs:
167+
return True
168+
except fs.errors.CreateFailed:
169+
return False
170+
else:
171+
zip_file, folder = path.split(".tar/")
172+
try:
173+
with fs.open_fs(zip_file + ".tar") as the_fs:
174+
return the_fs.exists(folder)
175+
except fs.errors.CreateFailed:
176+
return False
177+
148178
dir_name = fs.path.dirname(path)
149179
the_file_name = fs.path.basename(path)
150180

moban/mobanfile/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def _iterate_list_of_dicts(list_of_dict):
109109

110110
def handle_targets(merged_options, targets):
111111
list_of_templating_parameters = parse_targets(merged_options, targets)
112+
112113
jobs_for_each_engine = OrderedDict()
113114

114115
for target in list_of_templating_parameters:

moban/mobanfile/targets.py

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def parse_targets(options, targets):
4040

4141

4242
def _handle_explicit_target(options, target):
43+
4344
common_data_file = options[constants.LABEL_CONFIG]
4445
default_template_type = options[constants.LABEL_TEMPLATE_TYPE]
4546
template_file = target.get(

moban/mobanfile/templates.py

+62-8
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,21 @@
88

99
def handle_template(template_file, output, template_dirs):
1010
log.info("handling %s" % template_file)
11+
1112
if template_file.endswith("**"):
1213
source_dir = template_file[:-3]
1314
src_path = find_file_in_template_dirs(source_dir, template_dirs)
1415
if src_path:
15-
for a_triple in _listing_directory_files_recusively(
16-
source_dir, src_path, output
17-
):
18-
yield a_triple
16+
if "zip://" in src_path:
17+
for a_triple in _listing_zip_directory_files_recusively(
18+
source_dir, src_path, output
19+
):
20+
yield a_triple
21+
else:
22+
for a_triple in _listing_directory_files_recusively(
23+
source_dir, src_path, output
24+
):
25+
yield a_triple
1926
else:
2027
reporter.report_error_message(
2128
"{0} cannot be found".format(template_file)
@@ -30,10 +37,16 @@ def handle_template(template_file, output, template_dirs):
3037
"{0} cannot be found".format(template_file)
3138
)
3239
elif file_system.is_dir(template_file_on_disk):
33-
for a_triple in _list_dir_files(
34-
template_file, template_file_on_disk, output
35-
):
36-
yield a_triple
40+
if "zip://" in template_file_on_disk:
41+
for a_triple in _list_zip_dir_files(
42+
template_file, template_file_on_disk, output
43+
):
44+
yield a_triple
45+
else:
46+
for a_triple in _list_dir_files(
47+
template_file, template_file_on_disk, output
48+
):
49+
yield a_triple
3750
else:
3851
template_type = _get_template_type(template_file)
3952
yield (template_file, output, template_type)
@@ -54,6 +67,24 @@ def _list_dir_files(source, actual_source_path, dest):
5467
yield (src_file_under_dir, dest_file_under_dir, template_type)
5568

5669

70+
def _list_zip_dir_files(source, actual_source_path, dest):
71+
zip_file, folder = actual_source_path.split(".zip/")
72+
with file_system.open_fs(zip_file + ".zip") as fs_handle:
73+
for file_name in fs_handle.listdir(file_system.to_unicode(folder)):
74+
if fs_handle.isfile(
75+
file_system.to_unicode(folder + "/" + file_name)
76+
):
77+
# please note jinja2 does NOT like windows path
78+
# hence the following statement looks like cross platform
79+
# src_file_under_dir = os.path.join(source, file_name)
80+
# but actually it breaks windows instead.
81+
src_file_under_dir = "%s/%s" % (source, file_name)
82+
83+
dest_file_under_dir = dest + "/" + file_name
84+
template_type = _get_template_type(src_file_under_dir)
85+
yield (src_file_under_dir, dest_file_under_dir, template_type)
86+
87+
5788
def _listing_directory_files_recusively(source, actual_source_path, dest):
5889
with file_system.open_fs(actual_source_path) as fs_handle:
5990
for file_name in fs_handle.listdir(u"."):
@@ -70,6 +101,29 @@ def _listing_directory_files_recusively(source, actual_source_path, dest):
70101
yield a_triple
71102

72103

104+
def _listing_zip_directory_files_recusively(source, actual_source_path, dest):
105+
zip_file, folder = actual_source_path.split(".zip/")
106+
print(actual_source_path)
107+
108+
with file_system.open_fs(zip_file + ".zip") as fs_handle:
109+
for file_name in fs_handle.listdir(file_system.to_unicode(folder)):
110+
src_file_under_dir = source + "/" + file_name
111+
dest_file_under_dir = dest + "/" + file_name
112+
real_src_file = "%s/%s" % (actual_source_path, file_name)
113+
if fs_handle.isfile(
114+
file_system.to_unicode(folder + "/" + file_name)
115+
):
116+
template_type = _get_template_type(src_file_under_dir)
117+
yield (src_file_under_dir, dest_file_under_dir, template_type)
118+
elif fs_handle.isdir(
119+
file_system.to_unicode(folder + "/" + file_name)
120+
):
121+
for a_triple in _listing_zip_directory_files_recusively(
122+
src_file_under_dir, real_src_file, dest_file_under_dir
123+
):
124+
yield a_triple
125+
126+
73127
def _get_template_type(template_file):
74128
_, extension = file_system.path_splitext(template_file)
75129
if extension:

moban/plugins/template.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from lml.plugin import PluginManager
77
from moban.hashstore import HASH_STORE
88
from moban.deprecated import deprecated
9+
from moban.buffered_writer import BufferedWriter
910
from moban.plugins.context import Context
1011
from moban.plugins.library import LIBRARIES
1112
from moban.plugins.strategy import Strategy
@@ -68,6 +69,7 @@ def __init__(self, template_dirs, context_dirs, engine):
6869
self.engine = engine
6970
self.templated_count = 0
7071
self.file_count = 0
72+
self.buffered_writer = BufferedWriter()
7173

7274
def report(self):
7375
if self.templated_count == 0:
@@ -96,6 +98,7 @@ def render_to_file(self, template_file, data_file, output_file):
9698
if flag:
9799
reporter.report_templating(template_file, output_file)
98100
self.templated_count += 1
101+
self.buffered_writer.close()
99102

100103
def render_string_to_file(
101104
self, template_in_string, data_file, output_file
@@ -110,6 +113,7 @@ def render_string_to_file(
110113
if flag:
111114
reporter.report_templating(template_abs_path, output_file)
112115
self.templated_count += 1
116+
self.buffered_writer.close()
113117

114118
def apply_template(self, template_abs_path, template, data, output_file):
115119
rendered_content = self.engine.apply_template(
@@ -124,21 +128,26 @@ def apply_template(self, template_abs_path, template, data, output_file):
124128
output_file, rendered_content, template_abs_path
125129
)
126130
if flag:
127-
utils.write_file_out(output_file, rendered_content)
128-
utils.file_permissions_copy(template_abs_path, output_file)
131+
self.buffered_writer.write_file_out(
132+
output_file, rendered_content
133+
)
134+
if "zip://" not in output_file and "tar://" not in output_file:
135+
utils.file_permissions_copy(template_abs_path, output_file)
129136
return flag
130137
except exceptions.FileNotFound:
131-
utils.write_file_out(output_file, rendered_content)
138+
self.buffered_writer.write_file_out(output_file, rendered_content)
132139
return True
133140

134141
def render_to_files(self, array_of_template_targets):
135142
sta = Strategy(array_of_template_targets)
143+
136144
sta.process()
137145
choice = sta.what_to_do()
138146
if choice == Strategy.DATA_FIRST:
139147
self._render_with_finding_data_first(sta.data_file_index)
140148
else:
141149
self._render_with_finding_template_first(sta.template_file_index)
150+
self.buffered_writer.close()
142151

143152
def _render_with_finding_template_first(self, template_file_index):
144153
for (template_file, data_output_pairs) in template_file_index.items():

0 commit comments

Comments
 (0)