Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
O
OpnSense
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kulya
OpnSense
Commits
f763963c
Commit
f763963c
authored
Feb 01, 2016
by
Ad Schellevis
Committed by
Franco Fichtner
Feb 17, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
(configd) code style cleanups
(cherry picked from commit
9c84f2b4
)
parent
52093041
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
93 additions
and
67 deletions
+93
-67
__init__.py
src/opnsense/service/modules/__init__.py
+5
-0
template_helpers.py
src/opnsense/service/modules/addons/template_helpers.py
+3
-1
config.py
src/opnsense/service/modules/config.py
+10
-9
daemonize.py
src/opnsense/service/modules/daemonize.py
+1
-0
ph_inline_actions.py
src/opnsense/service/modules/ph_inline_actions.py
+2
-2
processhandler.py
src/opnsense/service/modules/processhandler.py
+54
-39
template.py
src/opnsense/service/modules/template.py
+18
-16
No files found.
src/opnsense/service/modules/__init__.py
View file @
f763963c
...
...
@@ -24,12 +24,17 @@
POSSIBILITY OF SUCH DAMAGE.
"""
def
singleton
(
cls
,
*
args
,
**
kwargs
):
""" singleton pattern, use ad decorator
:param cls:
"""
instances
=
{}
# noinspection PyShadowingNames
def
getinstance
(
*
args
,
**
kwargs
):
if
cls
not
in
instances
:
instances
[
cls
]
=
cls
(
*
args
,
**
kwargs
)
return
instances
[
cls
]
return
getinstance
src/opnsense/service/modules/addons/template_helpers.py
View file @
f763963c
...
...
@@ -30,6 +30,8 @@
from
operator
import
itemgetter
# noinspection PyPep8Naming
class
Helpers
(
object
):
def
__init__
(
self
,
template_in_data
):
""" initialize template helpers
...
...
@@ -82,7 +84,7 @@ class Helpers(object):
return
result
else
:
# resort list by tag
return
sorted
(
result
,
key
=
itemgetter
(
sortBy
))
return
sorted
(
result
,
key
=
itemgetter
(
sortBy
))
def
getUUIDtag
(
self
,
uuid
):
""" retrieve tag name of registered uuid, returns __not_found__ if none available
...
...
src/opnsense/service/modules/config.py
View file @
f763963c
...
...
@@ -28,14 +28,14 @@
package : configd
function: config handler
"""
__author__
=
'Ad Schellevis'
import
os
import
stat
import
collections
import
copy
import
xml.etree.cElementTree
as
ElementTree
__author__
=
'Ad Schellevis'
class
Config
(
object
):
def
__init__
(
self
,
filename
):
...
...
@@ -62,14 +62,14 @@ class Config(object):
self
.
_config_data
[
'__uuid_tags__'
]
=
self
.
__uuid_tags
self
.
_file_mod
=
mod_time
def
_traverse
(
self
,
xml
N
ode
):
def
_traverse
(
self
,
xml
_n
ode
):
""" traverse xml node and return ordered dictionary structure
:param xml
N
ode: ElementTree node
:param xml
_n
ode: ElementTree node
:return: collections.OrderedDict
"""
this_item
=
collections
.
OrderedDict
()
if
len
(
list
(
xml
N
ode
))
>
0
:
for
item
in
list
(
xml
N
ode
):
if
len
(
list
(
xml
_n
ode
))
>
0
:
for
item
in
list
(
xml
_n
ode
):
item_content
=
self
.
_traverse
(
item
)
if
'uuid'
in
item
.
attrib
:
self
.
__uuid_data
[
item
.
attrib
[
'uuid'
]]
=
item_content
...
...
@@ -88,7 +88,7 @@ class Config(object):
this_item
[
item
.
tag
]
=
self
.
_traverse
(
item
)
else
:
# last node, return text
return
xml
N
ode
.
text
return
xml
_n
ode
.
text
return
this_item
...
...
@@ -98,14 +98,15 @@ class Config(object):
@param elem: cElementTree
@param level: Currentlevel
"""
i
=
"
\n
"
+
level
*
" "
i
=
"
\n
"
+
level
*
" "
if
len
(
elem
):
if
not
elem
.
text
or
not
elem
.
text
.
strip
():
elem
.
text
=
i
+
" "
for
e
in
elem
:
self
.
indent
(
e
,
level
+
1
)
self
.
indent
(
e
,
level
+
1
)
if
not
e
.
tail
or
not
e
.
tail
.
strip
():
e
.
tail
=
i
+
" "
# noinspection PyUnboundLocalVariable
if
not
e
.
tail
or
not
e
.
tail
.
strip
():
e
.
tail
=
i
else
:
...
...
src/opnsense/service/modules/daemonize.py
View file @
f763963c
...
...
@@ -58,6 +58,7 @@ class Daemonize(object):
def
sigterm
(
self
,
signum
,
frame
):
"""
These actions will be done after SIGTERM.
:param frame:
"""
self
.
logger
.
warn
(
"Caught signal
%
s. Stopping daemon."
%
signum
)
os
.
remove
(
self
.
pid
)
...
...
src/opnsense/service/modules/ph_inline_actions.py
View file @
f763963c
...
...
@@ -48,7 +48,7 @@ def execute(action, parameters):
# generate template
tmpl
=
template
.
Template
(
action
.
root_dir
)
conf
=
config
.
Config
(
action
.
config
)
tmpl
.
set
C
onfig
(
conf
.
get
())
tmpl
.
set
_c
onfig
(
conf
.
get
())
filenames
=
tmpl
.
generate
(
parameters
)
del
conf
...
...
@@ -78,7 +78,7 @@ def execute(action, parameters):
# list all available configd actions
from
processhandler
import
ActionHandler
act_handler
=
ActionHandler
()
actions
=
act_handler
.
list
A
ctions
([
'message'
,
'description'
])
actions
=
act_handler
.
list
_a
ctions
([
'message'
,
'description'
])
if
unicode
(
parameters
)
.
lower
()
==
'json'
:
import
json
...
...
src/opnsense/service/modules/processhandler.py
View file @
f763963c
...
...
@@ -28,8 +28,6 @@
function: unix domain socket process worker process
"""
__author__
=
'Ad Schellevis'
import
os
import
subprocess
import
socket
...
...
@@ -45,6 +43,8 @@ import tempfile
import
ph_inline_actions
from
modules
import
singleton
__author__
=
'Ad Schellevis'
class
Handler
(
object
):
""" Main handler class, opens unix domain socket and starts listening
...
...
@@ -57,7 +57,8 @@ class Handler(object):
-> execute ActionHandler command using Action objects
<- send back result string
"""
def
__init__
(
self
,
socket_filename
,
config_path
,
config_environment
=
{},
simulation_mode
=
False
):
def
__init__
(
self
,
socket_filename
,
config_path
,
config_environment
=
None
,
simulation_mode
=
False
):
""" Constructor
:param socket_filename: filename of unix domain socket to use
...
...
@@ -65,6 +66,8 @@ class Handler(object):
:param simulation_mode: emulation mode, do not start actual (script) commands
:return: object
"""
if
config_environment
is
None
:
config_environment
=
{}
self
.
socket_filename
=
socket_filename
self
.
config_path
=
config_path
self
.
simulation_mode
=
simulation_mode
...
...
@@ -77,10 +80,11 @@ class Handler(object):
:return:
"""
while
True
:
# noinspection PyBroadException
try
:
# open action handler
act
H
andler
=
ActionHandler
(
config_path
=
self
.
config_path
,
config_environment
=
self
.
config_environment
)
act
_h
andler
=
ActionHandler
(
config_path
=
self
.
config_path
,
config_environment
=
self
.
config_environment
)
# remove previous socket ( if exists )
try
:
...
...
@@ -99,7 +103,7 @@ class Handler(object):
# spawn a client connection
cmd_thread
=
HandlerClient
(
connection
=
connection
,
client_address
=
client_address
,
action_handler
=
act
H
andler
,
action_handler
=
act
_h
andler
,
simulation_mode
=
self
.
simulation_mode
)
if
self
.
single_threaded
:
# run single threaded
...
...
@@ -120,7 +124,7 @@ class Handler(object):
# cleanup on exit, remove socket
os
.
remove
(
self
.
socket_filename
)
return
except
:
except
Exception
:
# something went wrong... send traceback to syslog, restart listener (wait for a short time)
print
(
traceback
.
format_exc
())
syslog
.
syslog
(
syslog
.
LOG_ERR
,
'Handler died on
%
s'
%
traceback
.
format_exc
())
...
...
@@ -130,6 +134,7 @@ class Handler(object):
class
HandlerClient
(
threading
.
Thread
):
""" Handle commands via specified socket connection
"""
def
__init__
(
self
,
connection
,
client_address
,
action_handler
,
simulation_mode
=
False
):
"""
:param connection: socket connection object
...
...
@@ -155,6 +160,7 @@ class HandlerClient(threading.Thread):
exec_action
=
''
exec_params
=
''
exec_in_background
=
False
# noinspection PyBroadException
try
:
# receive command, maximum data length is 4k... longer messages will be truncated
data
=
self
.
connection
.
recv
(
4096
)
...
...
@@ -186,7 +192,7 @@ class HandlerClient(threading.Thread):
# execute requested action
if
self
.
simulation_mode
:
self
.
action_handler
.
show
A
ction
(
exec_command
,
exec_action
,
exec_params
,
self
.
message_uuid
)
self
.
action_handler
.
show
_a
ction
(
exec_command
,
exec_action
,
exec_params
,
self
.
message_uuid
)
result
=
'OK'
else
:
result
=
self
.
action_handler
.
execute
(
exec_command
,
exec_action
,
exec_params
,
self
.
message_uuid
)
...
...
@@ -207,23 +213,28 @@ class HandlerClient(threading.Thread):
except
SystemExit
:
# ignore system exit related errors
pass
except
:
except
Exception
:
print
(
traceback
.
format_exc
())
syslog
.
syslog
(
syslog
.
LOG_ERR
,
'unable to sendback response [
%
s] for [
%
s][
%
s][
%
s] {
%
s}, message was
%
s'
%
(
result
,
exec_command
,
exec_action
,
exec_params
,
self
.
message_uuid
,
traceback
.
format_exc
()))
syslog
.
syslog
(
syslog
.
LOG_ERR
,
'unable to sendback response [
%
s] for [
%
s][
%
s][
%
s] {
%
s}, message was
%
s'
%
(
result
,
exec_command
,
exec_action
,
exec_params
,
self
.
message_uuid
,
traceback
.
format_exc
()
)
)
finally
:
if
not
exec_in_background
:
self
.
connection
.
close
()
@
singleton
class
ActionHandler
(
object
):
""" Start/stop services and functions using configuration data defined in conf/actions_<topic>.conf
"""
def
__init__
(
self
,
config_path
=
None
,
config_environment
=
None
):
""" Initialize action handler to start system functions
...
...
@@ -238,6 +249,7 @@ class ActionHandler(object):
# try to load data on initial start
if
not
hasattr
(
self
,
'action_map'
):
self
.
action_map
=
{}
self
.
load_config
()
def
load_config
(
self
):
...
...
@@ -245,8 +257,6 @@ class ActionHandler(object):
:return: None
"""
self
.
action_map
=
{}
for
config_filename
in
glob
.
glob
(
'
%
s/actions_*.conf'
%
self
.
config_path
)
\
+
glob
.
glob
(
'
%
s/actions.d/actions_*.conf'
%
self
.
config_path
):
# this topic's name (service, filter, template, etc)
...
...
@@ -260,7 +270,7 @@ class ActionHandler(object):
cnf
.
read
(
config_filename
)
for
section
in
cnf
.
sections
():
# map configuration data on object
action_obj
=
Action
(
config_environment
=
self
.
config_environment
)
action_obj
=
Action
(
config_environment
=
self
.
config_environment
)
for
act_prop
in
cnf
.
items
(
section
):
setattr
(
action_obj
,
act_prop
[
0
],
act_prop
[
1
])
...
...
@@ -274,10 +284,13 @@ class ActionHandler(object):
for
alias
in
section
.
split
(
'|'
):
self
.
action_map
[
topic_name
][
alias
]
=
action_obj
def
list
Actions
(
self
,
attributes
=
[]
):
def
list
_actions
(
self
,
attributes
=
None
):
""" list all available actions
:param attributes:
:return: dict
"""
if
attributes
is
None
:
attributes
=
[]
result
=
{}
for
command
in
self
.
action_map
:
for
action
in
self
.
action_map
[
command
]:
...
...
@@ -285,7 +298,7 @@ class ActionHandler(object):
# parse second level actions
# TODO: nesting actions may be better to solve recursive in here and in load_config part
for
subAction
in
self
.
action_map
[
command
][
action
]:
cmd
=
'
%
s
%
s
%
s'
%
(
command
,
action
,
subAction
)
cmd
=
'
%
s
%
s
%
s'
%
(
command
,
action
,
subAction
)
result
[
cmd
]
=
{}
for
actAttr
in
attributes
:
if
hasattr
(
self
.
action_map
[
command
][
action
][
subAction
],
actAttr
):
...
...
@@ -293,7 +306,7 @@ class ActionHandler(object):
else
:
result
[
cmd
][
actAttr
]
=
''
else
:
cmd
=
'
%
s
%
s'
%
(
command
,
action
)
cmd
=
'
%
s
%
s'
%
(
command
,
action
)
result
[
cmd
]
=
{}
for
actAttr
in
attributes
:
if
hasattr
(
self
.
action_map
[
command
][
action
],
actAttr
):
...
...
@@ -303,7 +316,7 @@ class ActionHandler(object):
return
result
def
find
A
ction
(
self
,
command
,
action
,
parameters
):
def
find
_a
ction
(
self
,
command
,
action
,
parameters
):
""" find action object
:param command: command/topic for example interface
...
...
@@ -320,7 +333,7 @@ class ActionHandler(object):
# 3 level action ( "interface linkup start" for example )
if
isinstance
(
self
.
action_map
[
command
][
action
][
parameters
[
0
]],
Action
):
action_obj
=
self
.
action_map
[
command
][
action
][
parameters
[
0
]]
action_obj
.
set
ParameterStartP
os
(
1
)
action_obj
.
set
_parameter_start_p
os
(
1
)
elif
isinstance
(
self
.
action_map
[
command
][
action
],
Action
):
action_obj
=
self
.
action_map
[
command
][
action
]
...
...
@@ -336,17 +349,17 @@ class ActionHandler(object):
:return: OK on success, else error code
"""
action_params
=
[]
action_obj
=
self
.
find
A
ction
(
command
,
action
,
parameters
)
action_obj
=
self
.
find
_a
ction
(
command
,
action
,
parameters
)
if
action_obj
is
not
None
:
if
parameters
is
not
None
and
len
(
parameters
)
>
action_obj
.
get
ParameterStartP
os
():
action_params
=
parameters
[
action_obj
.
get
ParameterStartP
os
():]
if
parameters
is
not
None
and
len
(
parameters
)
>
action_obj
.
get
_parameter_start_p
os
():
action_params
=
parameters
[
action_obj
.
get
_parameter_start_p
os
():]
return
'
%
s
\n
'
%
action_obj
.
execute
(
action_params
,
message_uuid
)
return
'Action not found
\n
'
def
show
A
ction
(
self
,
command
,
action
,
parameters
,
message_uuid
):
def
show
_a
ction
(
self
,
command
,
action
,
parameters
,
message_uuid
):
""" debug/simulation mode: show action information
:param command: command/topic for example interface
:param action: action to run ( for example linkup )
...
...
@@ -354,10 +367,10 @@ class ActionHandler(object):
:param message_uuid: message unique id
:return: None
"""
action_obj
=
self
.
find
A
ction
(
command
,
action
,
parameters
)
action_obj
=
self
.
find
_a
ction
(
command
,
action
,
parameters
)
print
(
'---------------------------------------------------------------------'
)
print
(
'execute
%
s.
%
s with parameters :
%
s '
%
(
command
,
action
,
parameters
))
print
(
'action object
%
s (
%
s)
'
%
(
action_obj
,
action_obj
.
comman
d
))
print
(
'action object
%
s (
%
s)
%
s'
%
(
action_obj
,
action_obj
.
command
,
message_uui
d
))
print
(
'---------------------------------------------------------------------'
)
...
...
@@ -365,6 +378,7 @@ class Action(object):
""" Action class, handles actual (system) calls.
set command, parameters (template) type and log message
"""
def
__init__
(
self
,
config_environment
):
""" setup default properties
:param config_environment: environment to use
...
...
@@ -377,7 +391,7 @@ class Action(object):
self
.
message
=
None
self
.
_parameter_start_pos
=
0
def
set
ParameterStartP
os
(
self
,
pos
):
def
set
_parameter_start_p
os
(
self
,
pos
):
"""
:param pos: start position of parameter list
...
...
@@ -385,7 +399,7 @@ class Action(object):
"""
self
.
_parameter_start_pos
=
pos
def
get
ParameterStartP
os
(
self
):
def
get
_parameter_start_p
os
(
self
):
""" getter for _parameter_start_pos
:return: start position of parameter list ( first argument can be part of action to start )
"""
...
...
@@ -427,18 +441,18 @@ class Action(object):
# use quotes on parameters to prevent code injection
if
script_command
.
count
(
'
%
s'
)
>
len
(
parameters
):
# script command accepts more parameters then given, fill with empty parameters
for
i
in
range
(
script_command
.
count
(
'
%
s'
)
-
len
(
parameters
)):
for
i
in
range
(
script_command
.
count
(
'
%
s'
)
-
len
(
parameters
)):
parameters
.
append
(
""
)
elif
len
(
parameters
)
>
script_command
.
count
(
'
%
s'
):
# parameters then expected, fail execution
return
'Parameter mismatch'
# force escape of shell exploitable characters for all user parameters
for
escape_char
in
[
'`'
,
'$'
,
'!'
,
'('
,
')'
,
'|'
]:
for
escape_char
in
[
'`'
,
'$'
,
'!'
,
'('
,
')'
,
'|'
]:
for
i
in
range
(
len
(
parameters
[
0
:
script_command
.
count
(
'
%
s'
)])):
parameters
[
i
]
=
parameters
[
i
]
.
replace
(
escape_char
,
'
\\
%
s'
%
escape_char
)
parameters
[
i
]
=
parameters
[
i
]
.
replace
(
escape_char
,
'
\\
%
s'
%
escape_char
)
script_command
=
script_command
%
tuple
(
map
(
lambda
x
:
'"'
+
x
.
replace
(
'"'
,
'
\\
"'
)
+
'"'
,
script_command
=
script_command
%
tuple
(
map
(
lambda
x
:
'"'
+
x
.
replace
(
'"'
,
'
\\
"'
)
+
'"'
,
parameters
[
0
:
script_command
.
count
(
'
%
s'
)]))
if
self
.
type
.
lower
()
==
'script'
:
# execute script type command
...
...
@@ -460,14 +474,15 @@ class Action(object):
with
tempfile
.
NamedTemporaryFile
()
as
error_stream
:
with
tempfile
.
NamedTemporaryFile
()
as
output_stream
:
subprocess
.
check_call
(
script_command
,
env
=
self
.
config_environment
,
shell
=
True
,
stdout
=
output_stream
,
stderr
=
error_stream
)
stdout
=
output_stream
,
stderr
=
error_stream
)
output_stream
.
seek
(
0
)
error_stream
.
seek
(
0
)
script_output
=
output_stream
.
read
()
script_error_output
=
error_stream
.
read
()
if
len
(
script_error_output
)
>
0
:
syslog
.
syslog
(
syslog
.
LOG_ERR
,
'[
%
s] Script action stderr returned "
%
s"'
%
(
message_uuid
,
script_error_output
.
strip
()[:
255
])
syslog
.
syslog
(
syslog
.
LOG_ERR
,
'[
%
s] Script action stderr returned "
%
s"'
%
(
message_uuid
,
script_error_output
.
strip
()[:
255
])
)
return
script_output
except
Exception
as
script_exception
:
...
...
src/opnsense/service/modules/template.py
View file @
f763963c
...
...
@@ -28,9 +28,6 @@
package : configd
function: template handler, generate configuration files using templates
"""
__author__
=
'Ad Schellevis'
import
os
import
os.path
import
syslog
...
...
@@ -40,9 +37,10 @@ import codecs
import
jinja2
import
addons.template_helpers
__author__
=
'Ad Schellevis'
class
Template
(
object
):
class
Template
(
object
):
def
__init__
(
self
,
target_root_directory
=
"/"
):
""" constructor
:return:
...
...
@@ -54,11 +52,12 @@ class Template(object):
self
.
_target_root_directory
=
target_root_directory
# setup jinja2 environment
self
.
_template_dir
=
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
))
+
'/../templates/'
self
.
_template_dir
=
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
))
+
'/../templates/'
self
.
_j2_env
=
jinja2
.
Environment
(
loader
=
jinja2
.
FileSystemLoader
(
self
.
_template_dir
),
trim_blocks
=
True
,
extensions
=
[
"jinja2.ext.do"
,])
extensions
=
[
"jinja2.ext.do"
,
])
def
_read_manifest
(
self
,
filename
):
@
staticmethod
def
_read_manifest
(
filename
):
"""
:param filename: manifest filename (path/+MANIFEST)
...
...
@@ -72,7 +71,8 @@ class Template(object):
return
result
def
_read_targets
(
self
,
filename
):
@
staticmethod
def
_read_targets
(
filename
):
""" read raw target filename masks
:param filename: targets filename (path/+TARGETS)
...
...
@@ -118,18 +118,19 @@ class Template(object):
return
result
def
set
C
onfig
(
self
,
config_data
):
def
set
_c
onfig
(
self
,
config_data
):
""" set config data
:param config_data: config data as dictionary/list structure
:return: None
"""
if
type
(
config_data
)
in
(
dict
,
collections
.
OrderedDict
):
if
type
(
config_data
)
in
(
dict
,
collections
.
OrderedDict
):
self
.
_config
=
config_data
else
:
# no data given, reset
self
.
_config
=
{}
def
__find_string_tags
(
self
,
instr
):
@
staticmethod
def
__find_string_tags
(
instr
):
"""
:param instr: string with optional tags [field.$$]
:return:
...
...
@@ -195,7 +196,8 @@ class Template(object):
return
result
def
_create_directory
(
self
,
filename
):
@
staticmethod
def
_create_directory
(
filename
):
""" create directory
:param filename: create path for filename ( if not existing )
:return: None
...
...
@@ -232,7 +234,7 @@ class Template(object):
result_filenames
[
new_filename
]
=
copy
.
deepcopy
(
result_filenames
[
filename
])
result_filenames
[
new_filename
][
key
]
=
target_filters
[
target_filter
][
key
]
template_filename
=
'
%
s/
%
s'
%
(
module_name
.
replace
(
'.'
,
'/'
),
src_template
)
template_filename
=
'
%
s/
%
s'
%
(
module_name
.
replace
(
'.'
,
'/'
),
src_template
)
# parse template, make sure issues can be traced back to their origin
try
:
j2_page
=
self
.
_j2_env
.
get_template
(
template_filename
)
...
...
@@ -269,8 +271,8 @@ class Template(object):
# it was in the original template.
# It looks like Jinja sometimes isn't consistent on placing this last end-of-line in.
if
len
(
content
)
>
1
and
content
[
-
1
]
!=
'
\n
'
:
src_file
=
'
%
s
%
s'
%
(
self
.
_template_dir
,
template_filename
)
src_file_handle
=
open
(
src_file
,
'r'
)
src_file
=
'
%
s
%
s'
%
(
self
.
_template_dir
,
template_filename
)
src_file_handle
=
open
(
src_file
,
'r'
)
src_file_handle
.
seek
(
-
1
,
os
.
SEEK_END
)
last_bytes_template
=
src_file_handle
.
read
()
src_file_handle
.
close
()
...
...
@@ -299,7 +301,7 @@ class Template(object):
# direct match
do_generate
=
True
elif
wildcard_pos
==
-
1
and
len
(
module_name
)
<
len
(
template_name
)
\
and
'
%
s.'
%
module_name
==
template_name
[
0
:
len
(
module_name
)
+
1
]:
and
'
%
s.'
%
module_name
==
template_name
[
0
:
len
(
module_name
)
+
1
]:
# match child item
do_generate
=
True
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment