Wed, 07 Aug 2019 16:10:12 +0200
microbit: added the minimal filesystem commands which are supported by the BBC micro:bit.
--- a/eric6/MicroPython/MicroPythonCommandsInterface.py Wed Aug 07 16:08:46 2019 +0200 +++ b/eric6/MicroPython/MicroPythonCommandsInterface.py Wed Aug 07 16:10:12 2019 +0200 @@ -67,6 +67,8 @@ """ super(MicroPythonCommandsInterface, self).__init__(parent) + self.__repl = parent + self.__blockReadyRead = False self.__serial = MicroPythonSerialPort( @@ -291,11 +293,19 @@ @rtype tuple of str @exception IOError raised to indicate an issue with the device """ - commands = [ - "import os as __os_", - "print(__os_.listdir('{0}'))".format(dirname), - "del __os_", - ] + if self.__repl.isMicrobit(): + # BBC micro:bit does not support directories + commands = [ + "import os as __os_", + "print(__os_.listdir())", + "del __os_", + ] + else: + commands = [ + "import os as __os_", + "print(__os_.listdir('{0}'))".format(dirname), + "del __os_", + ] out, err = self.execute(commands) if err: raise IOError(self.__shortError(err)) @@ -317,35 +327,58 @@ @rtype tuple of (str, tuple) @exception IOError raised to indicate an issue with the device """ - commands = [ - "import os as __os_", - "\n".join([ - "def is_visible(filename):", - " return filename[0] != '.' and filename[-1] != '~'", - ]), - "\n".join([ - "def stat(filename):", - " try:", - " rstat = __os_.lstat(filename)", - " except:", - " rstat = __os_.stat(filename)", - " return tuple(rstat)", - ]), - "\n".join([ - "def listdir_stat(dirname):", - " try:", - " files = __os_.listdir(dirname)", - " except OSError:", - " return None", - " if dirname in ('', '/'):", - " return list((f, stat(f)) for f in files if" - " is_visible(f))", - " return list((f, stat(dirname + '/' + f)) for f in files" - " if is_visible(f))", - ]), - "print(listdir_stat('{0}'))".format(dirname), - "del __os_, stat, listdir_stat", - ] + if self.__repl.isMicrobit(): + # BBC micro:bit does not support directories + commands = [ + "import os as __os_", + "\n".join([ + "def is_visible(filename):", + " return filename[0] != '.' and filename[-1] != '~'", + ]), + "\n".join([ + "def stat(filename):", + " size = __os_.size(filename)", + " return (0, 0, 0, 0, 0, 0, size, 0, 0, 0)" + ]), + "\n".join([ + "def listdir_stat():", + " files = __os_.listdir()", + " return list((f, stat(f)) for f in files if" + " is_visible(f))", + ]), + "print(listdir_stat())", + "del __os_, stat, listdir_stat, is_visible", + ] + else: + commands = [ + "import os as __os_", + "\n".join([ + "def is_visible(filename):", + " return filename[0] != '.' and filename[-1] != '~'", + ]), + "\n".join([ + "def stat(filename):", + " try:", + " rstat = __os_.lstat(filename)", + " except:", + " rstat = __os_.stat(filename)", + " return tuple(rstat)", + ]), + "\n".join([ + "def listdir_stat(dirname):", + " try:", + " files = __os_.listdir(dirname)", + " except OSError:", + " return None", + " if dirname in ('', '/'):", + " return list((f, stat(f)) for f in files if" + " is_visible(f))", + " return list((f, stat(dirname + '/' + f))" + " for f in files if is_visible(f))", + ]), + "print(listdir_stat('{0}'))".format(dirname), + "del __os_, stat, listdir_stat, is_visible", + ] out, err = self.execute(commands) if err: raise IOError(self.__shortError(err)) @@ -385,6 +418,10 @@ @rtype str @exception IOError raised to indicate an issue with the device """ + if self.__repl.isMicrobit(): + # BBC micro:bit does not support directories + return "" + commands = [ "import os as __os_", "print(__os_.getcwd())",
--- a/eric6/MicroPython/MicroPythonFileManagerWidget.py Wed Aug 07 16:08:46 2019 +0200 +++ b/eric6/MicroPython/MicroPythonFileManagerWidget.py Wed Aug 07 16:10:12 2019 +0200 @@ -55,16 +55,24 @@ super(MicroPythonFileManagerWidget, self).__init__(parent) self.setupUi(self) + self.__repl = parent + self.syncButton.setIcon(UI.PixmapCache.getIcon("2rightarrow")) self.putButton.setIcon(UI.PixmapCache.getIcon("1rightarrow")) + self.putAsButton.setIcon(UI.PixmapCache.getIcon("putAs")) self.getButton.setIcon(UI.PixmapCache.getIcon("1leftarrow")) + self.getAsButton.setIcon(UI.PixmapCache.getIcon("getAs")) self.localUpButton.setIcon(UI.PixmapCache.getIcon("1uparrow")) self.localReloadButton.setIcon(UI.PixmapCache.getIcon("reload")) self.deviceUpButton.setIcon(UI.PixmapCache.getIcon("1uparrow")) self.deviceReloadButton.setIcon(UI.PixmapCache.getIcon("reload")) + self.deviceUpButton.setEnabled(not self.__repl.isMicrobit()) + self.putButton.setEnabled(False) + self.putAsButton.setEnabled(False) self.getButton.setEnabled(False) + self.getAsButton.setEnabled(False) self.localFileTreeWidget.header().setSortIndicator( 0, Qt.AscendingOrder) @@ -109,20 +117,23 @@ self.tr("Show Time"), self.__showLocalTime) self.__deviceMenu = QMenu(self) - self.__deviceMenu.addAction( - self.tr("Change Directory"), self.__changeDeviceDirectory) - self.__deviceMenu.addAction( - self.tr("Create Directory"), self.__createDeviceDirectory) - self.__devDelDirAct = self.__deviceMenu.addAction( - self.tr("Delete Directory"), self.__deleteDeviceDirectory) - self.__devDelDirTreeAct = self.__deviceMenu.addAction( - self.tr("Delete Directory Tree"), self.__deleteDeviceDirectoryTree) - self.__deviceMenu.addSeparator() + if not self.__repl.isMicrobit(): + self.__deviceMenu.addAction( + self.tr("Change Directory"), self.__changeDeviceDirectory) + self.__deviceMenu.addAction( + self.tr("Create Directory"), self.__createDeviceDirectory) + self.__devDelDirAct = self.__deviceMenu.addAction( + self.tr("Delete Directory"), self.__deleteDeviceDirectory) + self.__devDelDirTreeAct = self.__deviceMenu.addAction( + self.tr("Delete Directory Tree"), + self.__deleteDeviceDirectoryTree) + self.__deviceMenu.addSeparator() self.__devDelFileAct = self.__deviceMenu.addAction( self.tr("Delete File"), self.__deleteDeviceFile) - self.__deviceMenu.addSeparator() - self.__deviceMenu.addAction( - self.tr("Show Filesystem Info"), self.__showFileSystemInfo) + if not parent.isMicrobit(): + self.__deviceMenu.addSeparator() + self.__deviceMenu.addAction( + self.tr("Show Filesystem Info"), self.__showFileSystemInfo) def start(self): """ @@ -251,6 +262,7 @@ self.localFileTreeWidget.selectedItems()[0].text(0) .endswith("/")) self.putButton.setEnabled(enable) + self.putAsButton.setEnabled(enable) @pyqtSlot() def on_localUpButton_clicked(self): @@ -298,6 +310,7 @@ self.deviceFileTreeWidget.selectedItems()[0].text(0) .endswith("/")) self.getButton.setEnabled(enable) + self.getAsButton.setEnabled(enable) @pyqtSlot() def on_deviceUpButton_clicked(self): @@ -338,19 +351,35 @@ return False @pyqtSlot() - def on_putButton_clicked(self): + def on_putButton_clicked(self, putAs=False): """ Private slot to copy the selected file to the connected device. + + @param putAs flag indicating to give it a new name + @type bool """ selectedItems = self.localFileTreeWidget.selectedItems() if selectedItems: filename = selectedItems[0].text(0).strip() if not filename.endswith("/"): # it is really a file - if self.__isFileInList(filename, self.deviceFileTreeWidget): + if putAs: + deviceFilename, ok = QInputDialog.getText( + self, + self.tr("Put File As"), + self.tr("Enter a new name for the file"), + QLineEdit.Normal, + filename) + if not ok or not filename: + return + else: + deviceFilename = filename + + if self.__isFileInList(deviceFilename, + self.deviceFileTreeWidget): # ask for overwrite permission action, resultFilename = confirmOverwrite( - filename, self.tr("Copy File to Device"), + deviceFilename, self.tr("Copy File to Device"), self.tr("The given file exists already" " (Enter file name only)."), False, self) @@ -358,49 +387,79 @@ return elif action == "rename": deviceFilename = os.path.basename(resultFilename) - else: - deviceFilename = filename - else: - deviceFilename = filename + deviceCwd = self.deviceCwd.text() + if deviceCwd: + deviceFilename = deviceCwd + "/" + deviceFilename self.__fileManager.put( os.path.join(self.localCwd.text(), filename), - os.path.join(self.deviceCwd.text(), deviceFilename) + deviceFilename ) @pyqtSlot() - def on_getButton_clicked(self): + def on_putAsButton_clicked(self): + """ + Private slot to copy the selected file to the connected device + with a different name. + """ + self.on_putButton_clicked(putAs=True) + + @pyqtSlot() + def on_getButton_clicked(self, getAs=False): """ Private slot to copy the selected file from the connected device. + + @param getAs flag indicating to give it a new name + @type bool """ selectedItems = self.deviceFileTreeWidget.selectedItems() if selectedItems: filename = selectedItems[0].text(0).strip() if not filename.endswith("/"): # it is really a file - if self.__isFileInList(filename, self.localFileTreeWidget): + if getAs: + localFilename, ok = QInputDialog.getText( + self, + self.tr("Get File As"), + self.tr("Enter a new name for the file"), + QLineEdit.Normal, + filename) + if not ok or not filename: + return + else: + localFilename = filename + + if self.__isFileInList(localFilename, + self.localFileTreeWidget): # ask for overwrite permission action, resultFilename = confirmOverwrite( - filename, self.tr("Copy File from Device"), + localFilename, self.tr("Copy File from Device"), self.tr("The given file exists already."), True, self) if action == "cancel": return elif action == "rename": localFilename = resultFilename - else: - localFilename = filename - else: - localFilename = filename + deviceCwd = self.deviceCwd.text() + if deviceCwd: + filename = deviceCwd + "/" + filename if not os.path.isabs(localFilename): localFilename = os.path.join(self.localCwd.text(), localFilename) self.__fileManager.get( - os.path.join(self.deviceCwd.text(), filename), + filename, localFilename ) + @pyqtSlot() + def on_getAsButton_clicked(self): + """ + Private slot to copy the selected file from the connected device + with a different name. + """ + self.on_getButton_clicked(getAs=True) + @pyqtSlot(str, str) def __handleGetDone(self, deviceFile, localFile): """ @@ -630,8 +689,9 @@ else: isDir = False isFile = False - self.__devDelDirAct.setEnabled(isDir) - self.__devDelDirTreeAct.setEnabled(isDir) + if not self.__repl.isMicrobit(): + self.__devDelDirAct.setEnabled(isDir) + self.__devDelDirTreeAct.setEnabled(isDir) self.__devDelFileAct.setEnabled(isFile) self.__deviceMenu.exec_(self.deviceFileTreeWidget.mapToGlobal(pos)) @@ -710,7 +770,11 @@ """ if bool(len(self.deviceFileTreeWidget.selectedItems())): name = self.deviceFileTreeWidget.selectedItems()[0].text(0) - filename = self.deviceCwd.text() + "/" + name + dirname = self.deviceCwd.text() + if dirname: + filename = dirname + "/" + name + else: + filename = name dlg = DeleteFilesConfirmationDialog( self, self.tr("Delete File"),
--- a/eric6/MicroPython/MicroPythonFileManagerWidget.ui Wed Aug 07 16:08:46 2019 +0200 +++ b/eric6/MicroPython/MicroPythonFileManagerWidget.ui Wed Aug 07 16:10:12 2019 +0200 @@ -96,7 +96,7 @@ <property name="sizeHint" stdset="0"> <size> <width>20</width> - <height>40</height> + <height>13</height> </size> </property> </spacer> @@ -116,6 +116,13 @@ </widget> </item> <item> + <widget class="QToolButton" name="putAsButton"> + <property name="toolTip"> + <string>Press to copy the selected file to the device with a new name</string> + </property> + </widget> + </item> + <item> <widget class="QToolButton" name="getButton"> <property name="toolTip"> <string>Press to copy the selected file from the device</string> @@ -123,6 +130,13 @@ </widget> </item> <item> + <widget class="QToolButton" name="getAsButton"> + <property name="toolTip"> + <string>Press to copy the selected file from the device with a new name</string> + </property> + </widget> + </item> + <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -256,7 +270,9 @@ <tabstop>deviceFileTreeWidget</tabstop> <tabstop>syncButton</tabstop> <tabstop>putButton</tabstop> + <tabstop>putAsButton</tabstop> <tabstop>getButton</tabstop> + <tabstop>getAsButton</tabstop> <tabstop>localUpButton</tabstop> <tabstop>localReloadButton</tabstop> <tabstop>deviceUpButton</tabstop>
--- a/eric6/MicroPython/MicroPythonReplWidget.py Wed Aug 07 16:08:46 2019 +0200 +++ b/eric6/MicroPython/MicroPythonReplWidget.py Wed Aug 07 16:10:12 2019 +0200 @@ -307,6 +307,19 @@ """ return self.__interface + def isMicrobit(self): + """ + Public method to check, if the connected/selected device is a + BBC micro:bit. + + @return flag indicating a micro:bit device + rtype bool + """ + if self.__device and "micro:bit" in self.__device.deviceName(): + return True + + return False + @pyqtSlot(int) def on_deviceTypeComboBox_activated(self, index): """
--- a/eric6/MicroPython/MicrobitDevices.py Wed Aug 07 16:08:46 2019 +0200 +++ b/eric6/MicroPython/MicrobitDevices.py Wed Aug 07 16:10:12 2019 +0200 @@ -45,7 +45,7 @@ """ super(MicrobitDevice, self).setButtons() self.microPython.setActionButtons( - repl=True, chart=HAS_QTCHART) + run=True, repl=True, files=True, chart=HAS_QTCHART) def forceInterrupt(self): """ @@ -57,6 +57,15 @@ """ return True + def deviceName(self): + """ + Public method to get the name of the device. + + @return name of the device + @rtype str + """ + return self.tr("BBC micro:bit") + def canStartRepl(self): """ Public method to determine, if a REPL can be started. @@ -77,6 +86,26 @@ """ return True, "" + def canRunScript(self): + """ + Public method to determine, if a script can be executed. + + @return tuple containing a flag indicating it is safe to start a + Plotter and a reason why it cannot. + @rtype tuple of (bool, str) + """ + return True, "" + + def runScript(self, script): + """ + Public method to run the given Python script. + + @param script script to be executed + @type str + """ + pythonScript = script.split("\n") + self.sendCommands(pythonScript) + def canStartFileManager(self): """ Public method to determine, if a File Manager can be started. @@ -85,7 +114,7 @@ File Manager and a reason why it cannot. @rtype tuple of (bool, str) """ - return False, "" + return True, "" def getWorkspace(self): """