Do not include hidden files in listed gcode scripts
This commit is contained in:
parent
ab97abf13e
commit
83f66e133e
8 changed files with 194 additions and 5 deletions
|
|
@ -30,7 +30,7 @@ import logging
|
|||
import re
|
||||
import uuid
|
||||
|
||||
from octoprint.util import atomic_write
|
||||
from octoprint.util import atomic_write, is_hidden_path
|
||||
|
||||
_APPNAME = "OctoPrint"
|
||||
|
||||
|
|
@ -405,10 +405,12 @@ class Settings(object):
|
|||
return folder
|
||||
|
||||
def _init_script_templating(self):
|
||||
from jinja2 import Environment, BaseLoader, FileSystemLoader, ChoiceLoader, TemplateNotFound
|
||||
from jinja2.nodes import Include, Const
|
||||
from jinja2 import Environment, BaseLoader, ChoiceLoader, TemplateNotFound
|
||||
from jinja2.nodes import Include
|
||||
from jinja2.ext import Extension
|
||||
|
||||
from octoprint.util.jinja import FilteredFileSystemLoader
|
||||
|
||||
class SnippetExtension(Extension):
|
||||
tags = {"snippet"}
|
||||
fields = Include.fields
|
||||
|
|
@ -497,10 +499,14 @@ class Settings(object):
|
|||
else:
|
||||
return template
|
||||
|
||||
file_system_loader = FileSystemLoader(self.getBaseFolder("scripts"))
|
||||
path_filter = lambda path: not is_hidden_path(path)
|
||||
file_system_loader = FilteredFileSystemLoader(self.getBaseFolder("scripts"),
|
||||
path_filter=path_filter)
|
||||
settings_loader = SettingsScriptLoader(self)
|
||||
choice_loader = ChoiceLoader([file_system_loader, settings_loader])
|
||||
select_loader = SelectLoader(choice_loader, dict(bundled=settings_loader, file=file_system_loader))
|
||||
select_loader = SelectLoader(choice_loader,
|
||||
dict(bundled=settings_loader,
|
||||
file=file_system_loader))
|
||||
return RelEnvironment(loader=select_loader, extensions=[SnippetExtension])
|
||||
|
||||
def _get_script_template(self, script_type, name, source=False):
|
||||
|
|
|
|||
|
|
@ -614,6 +614,23 @@ def bom_aware_open(filename, encoding="ascii", mode="r", **kwargs):
|
|||
return codecs.open(filename, encoding=encoding, mode=mode, **kwargs)
|
||||
|
||||
|
||||
def is_hidden_path(path):
|
||||
filename = os.path.basename(path)
|
||||
if filename.startswith("."):
|
||||
return True
|
||||
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
import ctypes
|
||||
attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(path))
|
||||
assert attrs != -1
|
||||
return bool(attrs & 2)
|
||||
except (AttributeError, AssertionError):
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class RepeatedTimer(threading.Thread):
|
||||
"""
|
||||
This class represents an action that should be run repeatedly in an interval. It is similar to python's
|
||||
|
|
|
|||
50
src/octoprint/util/jinja.py
Normal file
50
src/octoprint/util/jinja.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# coding=utf-8
|
||||
from __future__ import absolute_import
|
||||
|
||||
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||
__copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms of the AGPLv3 License"
|
||||
|
||||
import os
|
||||
|
||||
from jinja2.loaders import FileSystemLoader, TemplateNotFound, split_template_path
|
||||
|
||||
class FilteredFileSystemLoader(FileSystemLoader):
|
||||
"""
|
||||
Jinja2 ``FileSystemLoader`` subclass that allows filtering templates.
|
||||
|
||||
Only such templates will be accessible for whose paths the provided
|
||||
``path_filter`` filter function returns True.
|
||||
|
||||
``path_filter`` will receive the actual path on disc and should behave just
|
||||
like callables provided to Python's internal ``filter`` function, returning
|
||||
``True`` if the path is cleared and ``False`` if it is supposed to be removed
|
||||
from results and hence ``filter(path_filter, iterable)`` should be
|
||||
equivalent to ``[item for item in iterable if path_filter(item)]``.
|
||||
|
||||
If ``path_filter`` is not set or not a ``callable``, the loader will
|
||||
behave just like the regular Jinja2 ``FileSystemLoader``.
|
||||
"""
|
||||
def __init__(self, searchpath, path_filter=None, **kwargs):
|
||||
FileSystemLoader.__init__(self, searchpath, **kwargs)
|
||||
self.path_filter = path_filter
|
||||
|
||||
def get_source(self, environment, template):
|
||||
if callable(self.path_filter):
|
||||
pieces = split_template_path(template)
|
||||
if not self._combined_filter(os.path.join(*pieces)):
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
return FileSystemLoader.get_source(self, environment, template)
|
||||
|
||||
def list_templates(self):
|
||||
result = FileSystemLoader.list_templates(self)
|
||||
|
||||
if callable(self.path_filter):
|
||||
result = sorted(filter(self._combined_filter, result))
|
||||
|
||||
return result
|
||||
|
||||
def _combined_filter(self, path):
|
||||
filter_results = map(lambda x: not os.path.exists(os.path.join(x, path)) or self.path_filter(os.path.join(x, path)),
|
||||
self.searchpath)
|
||||
return all(filter_results)
|
||||
1
tests/util/_files/jinja_test_data/.hidden_everywhere.txt
Normal file
1
tests/util/_files/jinja_test_data/.hidden_everywhere.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
hidden_everywhere
|
||||
1
tests/util/_files/jinja_test_data/normal_text.txt
Normal file
1
tests/util/_files/jinja_test_data/normal_text.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
normal_text
|
||||
1
tests/util/_files/jinja_test_data/not_a_text.dat
Normal file
1
tests/util/_files/jinja_test_data/not_a_text.dat
Normal file
|
|
@ -0,0 +1 @@
|
|||
not_a_text
|
||||
|
|
@ -164,3 +164,40 @@ class TestAtomicWrite(unittest.TestCase):
|
|||
mock_tempfile.assert_called_once_with(mode="w", prefix="foo", suffix="bar", delete=False)
|
||||
mock_file.close.assert_called_once_with()
|
||||
mock_move.assert_called_once_with("tempfile.tmp", "somefile.yaml")
|
||||
|
||||
class IsHiddenPathTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
import tempfile
|
||||
|
||||
self.basepath = tempfile.mkdtemp()
|
||||
|
||||
self.path_always_visible = os.path.join(self.basepath, "always_visible.txt")
|
||||
self.path_hidden_on_windows = os.path.join(self.basepath, "hidden_on_windows.txt")
|
||||
self.path_always_hidden = os.path.join(self.basepath, ".always_hidden.txt")
|
||||
|
||||
for attr in ("path_always_visible", "path_hidden_on_windows", "path_always_hidden"):
|
||||
path = getattr(self, attr)
|
||||
with open(path, "w+b") as f:
|
||||
f.write(attr)
|
||||
|
||||
import sys
|
||||
if sys.platform == "win32":
|
||||
# we use ctypes and the windows API to set the hidden attribute on the file
|
||||
# only hidden on windows
|
||||
import ctypes
|
||||
ctypes.windll.kernel32.SetFileAttributesW(unicode(self.path_hidden_on_windows), 2)
|
||||
|
||||
def tearDown(self):
|
||||
import shutil
|
||||
shutil.rmtree(self.basepath)
|
||||
|
||||
def test_is_hidden_path(self):
|
||||
self.assertFalse(octoprint.util.is_hidden_path(self.path_always_visible))
|
||||
self.assertTrue(octoprint.util.is_hidden_path(self.path_always_hidden))
|
||||
|
||||
import sys
|
||||
if sys.platform == "win32":
|
||||
self.assertTrue(octoprint.util.is_hidden_path(self.path_hidden_on_windows))
|
||||
else:
|
||||
self.assertFalse(octoprint.util.is_hidden_path(self.path_hidden_on_windows))
|
||||
|
|
|
|||
76
tests/util/test_jinja.py
Normal file
76
tests/util/test_jinja.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# coding=utf-8
|
||||
from __future__ import absolute_import
|
||||
|
||||
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||
__copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms of the AGPLv3 License"
|
||||
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import jinja2
|
||||
|
||||
from ddt import ddt, data, unpack
|
||||
|
||||
import octoprint.util.jinja
|
||||
|
||||
NONE_FILTER = None
|
||||
HIDDEN_FILTER = lambda x: not os.path.basename(x).startswith(".")
|
||||
NO_TXT_FILTER = lambda x: x.endswith(".txt")
|
||||
COMBINED_FILTER = lambda x: HIDDEN_FILTER(x) and NO_TXT_FILTER(x)
|
||||
|
||||
@ddt
|
||||
class FilteredFileSystemLoaderTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.basepath = os.path.join(os.path.abspath(os.path.dirname(__file__)), "_files", "jinja_test_data")
|
||||
self.environment = jinja2.Environment()
|
||||
|
||||
def loader_factory(self, path_filter):
|
||||
return octoprint.util.jinja.FilteredFileSystemLoader(self.basepath,
|
||||
path_filter=path_filter)
|
||||
|
||||
@data(
|
||||
(NONE_FILTER, [".hidden_everywhere.txt", "normal_text.txt", "not_a_text.dat"]),
|
||||
(HIDDEN_FILTER, ["normal_text.txt", "not_a_text.dat"]),
|
||||
(NO_TXT_FILTER, [".hidden_everywhere.txt", "normal_text.txt"]),
|
||||
(COMBINED_FILTER, ["normal_text.txt"])
|
||||
)
|
||||
@unpack
|
||||
def test_list_templates(self, path_filter, expected):
|
||||
loader = self.loader_factory(path_filter=path_filter)
|
||||
templates = loader.list_templates()
|
||||
self.assertListEqual(templates, expected)
|
||||
|
||||
@data(
|
||||
(NONE_FILTER, ((".hidden_everywhere.txt", True),
|
||||
("normal_text.txt", True),
|
||||
("not_a_text.dat", True))),
|
||||
(HIDDEN_FILTER, ((".hidden_everywhere.txt", False),
|
||||
("normal_text.txt", True),
|
||||
("not_a_text.dat", True))),
|
||||
(NO_TXT_FILTER, ((".hidden_everywhere.txt", True),
|
||||
("normal_text.txt", True),
|
||||
("not_a_text.dat", False))),
|
||||
(COMBINED_FILTER, ((".hidden_everywhere.txt", False),
|
||||
("normal_text.txt", True),
|
||||
("not_a_text.dat", False)))
|
||||
)
|
||||
@unpack
|
||||
def test_get_source_none_filter(self, path_filter, param_sets):
|
||||
loader = self.loader_factory(path_filter=path_filter)
|
||||
for param_set in param_sets:
|
||||
template, success = param_set
|
||||
if success:
|
||||
self._test_get_source_success(loader, template)
|
||||
else:
|
||||
self._test_get_source_notfound(loader, template)
|
||||
|
||||
def _test_get_source_success(self, loader, template):
|
||||
loader.get_source(self.environment, template)
|
||||
|
||||
def _test_get_source_notfound(self, loader, template):
|
||||
try:
|
||||
loader.get_source(self.environment, template)
|
||||
self.fail("Expected an exception")
|
||||
except jinja2.TemplateNotFound:
|
||||
pass
|
||||
Loading…
Reference in a new issue