diff --git a/scripting/CppunitTest_scripting_pythonscript.mk b/scripting/CppunitTest_scripting_pythonscript.mk new file mode 100644 index 000000000000..f87d2d70e14a --- /dev/null +++ b/scripting/CppunitTest_scripting_pythonscript.mk @@ -0,0 +1,52 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,scripting_pythonscript)) + +$(eval $(call gb_CppunitTest_add_exception_objects,scripting_pythonscript, \ + scripting/qa/cppunit/pythonscript/PythonScriptTest \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,scripting_pythonscript, \ + cppu \ + cppuhelper \ + pythonloader \ + pyuno \ + sal \ + subsequenttest \ + test \ + unotest \ + utl \ + tl \ +)) + +$(eval $(call gb_CppunitTest_use_externals,scripting_pythonscript,\ + boost_headers \ + libxml2 \ +)) + +$(eval $(call gb_CppunitTest_use_api,scripting_pythonscript,\ + udkapi \ + offapi \ +)) + +$(eval $(call gb_CppunitTest_use_packages,scripting_pythonscript,scripting_scriptproviderforpython)) +$(call gb_CppunitTest_get_target,scripting_pythonscript): \ + UNO_SERVICES += $(SRCDIR)/scripting/source/pyprov/scriptproviderforpython.rdb + +$(eval $(call gb_CppunitTest_use_components,scripting_pythonscript,\ + pyuno/source/loader/pythonloader \ +)) + +$(eval $(call gb_CppunitTest_use_ure,scripting_pythonscript)) +$(eval $(call gb_CppunitTest_use_vcl,scripting_pythonscript)) +$(eval $(call gb_CppunitTest_use_rdb,scripting_pythonscript,services)) +$(eval $(call gb_CppunitTest_use_configuration,scripting_pythonscript)) + +# vim: set noet sw=4 ts=4: diff --git a/scripting/Module_scripting.mk b/scripting/Module_scripting.mk index 044432db72a4..4d27b702c1f3 100644 --- a/scripting/Module_scripting.mk +++ b/scripting/Module_scripting.mk @@ -50,6 +50,7 @@ $(eval $(call gb_Module_add_targets,scripting,\ $(eval $(call gb_Module_add_check_targets,scripting,\ $(if $(ENABLE_QUICKJS),CppunitTest_scripting_jsprov) \ $(if $(ENABLE_SCRIPTING_BEANSHELL),CppunitTest_scripting_beanshell) \ + $(if $(DISABLE_PYTHON),,CppunitTest_scripting_pythonscript) \ )) $(eval $(call gb_Module_add_subsequentcheck_targets,scripting,\ diff --git a/scripting/qa/cppunit/pythonscript/PythonScriptTest.cxx b/scripting/qa/cppunit/pythonscript/PythonScriptTest.cxx new file mode 100644 index 000000000000..1de73580596d --- /dev/null +++ b/scripting/qa/cppunit/pythonscript/PythonScriptTest.cxx @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +class PythonScriptTest : public UnoApiTest +{ +public: + PythonScriptTest() + : UnoApiTest(u"/scripting/qa/extras"_ustr) + { + } + + void setUp() override; + void tearDown() override; + +private: + css::uno::Reference m_xFileAccess; + css::uno::Reference m_xUriHelper; + css::uno::Reference m_xScriptProvider; + + void createDummyMacro(); + + void testCopyable(); + + CPPUNIT_TEST_SUITE(PythonScriptTest); + CPPUNIT_TEST(testCopyable); + CPPUNIT_TEST_SUITE_END(); +}; + +void PythonScriptTest::setUp() +{ + UnoApiTest::setUp(); + + m_xFileAccess = css::ucb::SimpleFileAccess::create(m_xContext); + + // The ExpandContentProvider is needed for ScriptURIHelper to work + css::uno::Reference xUcb + = css::ucb::UniversalContentBroker::create(m_xContext); + css::uno::Reference xExpandProvider( + m_xSFactory->createInstance(u"com.sun.star.ucb.ExpandContentProvider"_ustr), + css::uno::UNO_QUERY_THROW); + xUcb->registerContentProvider(xExpandProvider, u"vnd.sun.star.expand"_ustr, true); + + m_xUriHelper = css::script::provider::ScriptURIHelper::create(getComponentContext(), + u"Python"_ustr, u"user"_ustr); + + css::uno::Sequence aArgs(1); + aArgs.getArray()[0] <<= u"user"_ustr; + + m_xScriptProvider.set( + m_xFactory->createInstanceWithArgumentsAndContext( + "com.sun.star.script.provider.ScriptProviderForPython", aArgs, m_xContext), + css::uno::UNO_QUERY_THROW); +} + +void PythonScriptTest::tearDown() +{ + m_xScriptProvider.clear(); + + // Delete the Python scripting directory so that each test can have a clean slate + if (m_xFileAccess->isFolder(m_xUriHelper->getRootStorageURI())) + m_xFileAccess->kill(m_xUriHelper->getRootStorageURI()); + + m_xFileAccess.clear(); + m_xUriHelper.clear(); + + UnoApiTest::tearDown(); +} + +void PythonScriptTest::createDummyMacro() +{ + // Create a dummy macro in the script directory + css::uno::Reference xOutput = m_xFileAccess->openFileWrite( + m_xUriHelper->getRootStorageURI() + "/MyLibrary/MyScript.py"); + css::uno::Reference xTextOutput + = css::io::TextOutputStream::create(m_xContext); + xTextOutput->setEncoding(u"UTF-8"_ustr); + + css::uno::Reference xActiveDataSource(xTextOutput, + css::uno::UNO_QUERY_THROW); + xActiveDataSource->setOutputStream(xOutput); + + xTextOutput->writeString(u"def MyMacro():\n pass\n"_ustr); + xTextOutput->closeOutput(); +} + +bool isCopyableNode(const css::uno::Reference& xNode) +{ + css::uno::Reference xCopyable(xNode, + css::uno::UNO_QUERY); + + return xCopyable.is() && xCopyable->isCopyableNode(); +} + +void PythonScriptTest::testCopyable() +{ + createDummyMacro(); + + css::uno::Reference xRootNode(m_xScriptProvider, + css::uno::UNO_QUERY_THROW); + + // The root node shouldn’t be copyable + CPPUNIT_ASSERT(!isCopyableNode(xRootNode)); + + css::uno::Reference xLibrary = xRootNode->getChildNodes()[0]; + CPPUNIT_ASSERT_EQUAL(u"MyLibrary"_ustr, xLibrary->getName()); + + // The library shouldn’t be copyable + CPPUNIT_ASSERT(!isCopyableNode(xLibrary)); + + css::uno::Reference xFile = xLibrary->getChildNodes()[0]; + CPPUNIT_ASSERT_EQUAL(u"MyScript"_ustr, xFile->getName()); + + // File nodes are copyable + CPPUNIT_ASSERT(isCopyableNode(xFile)); + + css::uno::Reference xMacro = xFile->getChildNodes()[0]; + CPPUNIT_ASSERT_EQUAL(u"MyMacro"_ustr, xMacro->getName()); + + // Macro nodes are not copyable + CPPUNIT_ASSERT(!isCopyableNode(xMacro)); + + css::uno::Reference xCopyable( + xFile, css::uno::UNO_QUERY_THROW); + + // We can copy to the root level of the Python hierarchy + CPPUNIT_ASSERT(xCopyable->nodeCanBeCopiedTo(xRootNode)); + // We can’t copy to the same directory that the file is already in + CPPUNIT_ASSERT(!xCopyable->nodeCanBeCopiedTo(xLibrary)); + // We can’t copy to a file + CPPUNIT_ASSERT(!xCopyable->nodeCanBeCopiedTo(xFile)); + // or a macro + CPPUNIT_ASSERT(!xCopyable->nodeCanBeCopiedTo(xMacro)); + + // Make a new library that we can copy to + m_xFileAccess->createFolder(m_xUriHelper->getRootStorageURI() + "/MyOtherLibrary"); + css::uno::Reference xOtherLibrary; + for (const auto& xChild : xRootNode->getChildNodes()) + { + if (xChild->getName() == u"MyOtherLibrary"_ustr) + { + xOtherLibrary = xChild; + break; + } + } + CPPUNIT_ASSERT(xOtherLibrary.is()); + + // We should be able to copy into the new library + CPPUNIT_ASSERT(xCopyable->nodeCanBeCopiedTo(xOtherLibrary)); + css::uno::Reference xCopiedNode + = xCopyable->copyNode(xOtherLibrary); + + CPPUNIT_ASSERT_EQUAL(u"MyScript"_ustr, xCopiedNode->getName()); + + css::uno::Reference xCopiedMacro + = xCopiedNode->getChildNodes()[0]; + CPPUNIT_ASSERT_EQUAL(u"MyMacro"_ustr, xCopiedMacro->getName()); + + // Get the URI of the copied macro + css::uno::Reference xPropertySet(xCopiedMacro, + css::uno::UNO_QUERY_THROW); + css::uno::Any xURI = xPropertySet->getPropertyValue(u"URI"_ustr); + OUString sURI; + CPPUNIT_ASSERT(xURI >>= sURI); + + // The URI should contain the new library name + CPPUNIT_ASSERT(sURI.indexOf(":MyOtherLibrary|") != -1); + + // Check that we can load the script. This should also end up checking that the contents of the + // file contains the macro. + CPPUNIT_ASSERT(m_xScriptProvider->getScript(sURI).is()); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(PythonScriptTest); + +} // namespace +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/scripting/source/pyprov/pythonscript.py b/scripting/source/pyprov/pythonscript.py index d8bebc45e09c..3d61cda2067f 100644 --- a/scripting/source/pyprov/pythonscript.py +++ b/scripting/source/pyprov/pythonscript.py @@ -42,7 +42,8 @@ from com.sun.star.uno import Exception as UnoException from com.sun.star.awt import XActionListener from com.sun.star.script.provider import XScriptProvider, XScript, ScriptFrameworkErrorException -from com.sun.star.script.browse import XBrowseNode, XCreatableBrowseNode, XEditableBrowseNode +from com.sun.star.script.browse import XBrowseNode, XCreatableBrowseNode, XCopyableBrowseNode +from com.sun.star.script.browse import XEditableBrowseNode from com.sun.star.script.browse.BrowseNodeTypes import SCRIPT, CONTAINER from com.sun.star.system import SystemShellExecuteFlags @@ -715,7 +716,7 @@ class ScriptBrowseNode(unohelper.Base, XBrowseNode, XPropertySet, EditableNode): # ------------------------------------------------------- -class FileBrowseNode(unohelper.Base, XBrowseNode, EditableNode): +class FileBrowseNode(unohelper.Base, XBrowseNode, XCopyableBrowseNode, EditableNode): def __init__(self, provCtx, uri, name): self.provCtx = provCtx self.uri = uri @@ -750,6 +751,36 @@ class FileBrowseNode(unohelper.Base, XBrowseNode, EditableNode): def getType(self): return CONTAINER + def isCopyableNode(self): + return True + + def getCopyDestinationUrl(self, xDest): + if isinstance(xDest, PythonScriptProvider): + xDest = xDest.dirBrowseNode + if not isinstance(xDest, DirBrowseNode): + return None + + # Don’t allow copying into the same directory + if xDest.rootUrl == self.uri[0:self.uri.rfind('/')]: + return None + + return xDest.rootUrl + + def nodeCanBeCopiedTo(self, parentNode): + return self.getCopyDestinationUrl(parentNode) != None + + def copyNode(self, parentNode): + dest_dir_url = self.getCopyDestinationUrl(parentNode) + if dest_dir_url is None: + raise IllegalArgumentException("Invalid parent node passed to copyNode", self, 0) + + self.provCtx.sfa.createFolder(dest_dir_url) + + target_url = f"{dest_dir_url}/{self.uri[self.uri.rfind('/') + 1:]}" + self.provCtx.sfa.copy(self.uri, target_url) + + return FileBrowseNode(self.provCtx, target_url, self.name) + class DirBrowseNode(unohelper.Base, XBrowseNode, XCreatableBrowseNode): def __init__(self, provCtx, name, rootUrl):