Do not include hidden files in listed gcode scripts

This commit is contained in:
Gina Häußge 2015-11-19 18:58:56 +01:00
parent ab97abf13e
commit 83f66e133e
8 changed files with 194 additions and 5 deletions

View file

@ -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):

View file

@ -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

View 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)

View file

@ -0,0 +1 @@
hidden_everywhere

View file

@ -0,0 +1 @@
normal_text

View file

@ -0,0 +1 @@
not_a_text

View file

@ -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
View 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