tl; dr Необходимо редактировать Safari's Bookmarks.plist
для программного создания закладок.Ознакомьтесь с разделом «Использование скрипта Python» ниже.Это влечет за собой использование таблицы стилей XSLT в скрипте Bash и вызов ее через ваш файл .py
.Все инструменты, необходимые для достижения этой цели, встроены в macOS.
Важно: Используя macOS Mojave (10.14.x) +
, вам необходимо выполнить шаги 1-10 в разделе "MacOS Mojave Ограничения "раздел ниже.Эти изменения позволяют вносить изменения в Bookmarks.plist
.
Прежде чем продолжить, создайте копию Bookmarks.plist
, которую можно найти по адресу ~/Library/Safari/Bookmarks.plist
.Вы можете выполнить следующую команду, чтобы скопировать ее на Рабочий стол :
cp ~/Library/Safari/Bookmarks.plist ~/Desktop/Bookmarks.plist
Чтобы восстановить Bookmarks.plist
, позже запустите:
cp ~/Desktop/Bookmarks.plist ~/Library/Safari/Bookmarks.plist
Списки свойств
MacOS имеет встроенные инструменты командной строки, относящиеся к списку свойств (.plist
), а именно plutil
и defaults
, которые позволяют редактировать настройки приложения.которые обычно содержат плоские структуры данных.Однако Safari Bookmarks.plist
имеет глубоко вложенную структуру, которую ни один из этих инструментов не умеет редактировать.
Транформирование .plist
файлов в XML
plutil
обеспечиваетопция -convert
для преобразования .plist
из двоичного файла в XML.Например:
plutil -convert xml1 ~/Library/Safari/Bookmarks.plist
Аналогично, следующая команда преобразуется в двоичную:
plutil -convert binary1 ~/Library/Safari/Bookmarks.plist
Преобразование в XML позволяет использовать XSLT , который идеально подходит для преобразования сложного XMLструктуры.
Использование таблицы стилей XSLT
Эта пользовательская таблица стилей XSLT преобразует Bookmarks.plist
добавление узлов элементов для создания закладок:
template.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output
method="xml"
indent="yes"
doctype-system="http://www.apple.com/DTDs/PropertyList-1.0.dtd"
doctype-public="-//Apple//DTD PLIST 1.0//EN"/>
<xsl:param name="bkmarks-folder"/>
<xsl:param name="bkmarks"/>
<xsl:param name="guid"/>
<xsl:param name="keep-existing" select="false" />
<xsl:variable name="bmCount">
<xsl:value-of select="string-length($bkmarks) -
string-length(translate($bkmarks, ',', '')) + 1"/>
</xsl:variable>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template name="getNthValue">
<xsl:param name="list"/>
<xsl:param name="n"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="$n = 1">
<xsl:value-of select=
"substring-before(concat($list, $delimiter), $delimiter)"/>
</xsl:when>
<xsl:when test="contains($list, $delimiter) and $n > 1">
<!-- recursive call -->
<xsl:call-template name="getNthValue">
<xsl:with-param name="list"
select="substring-after($list, $delimiter)"/>
<xsl:with-param name="n" select="$n - 1"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="createBmEntryFragment">
<xsl:param name="loopCount" select="1"/>
<xsl:variable name="bmInfo">
<xsl:call-template name="getNthValue">
<xsl:with-param name="list" select="$bkmarks"/>
<xsl:with-param name="delimiter" select="','"/>
<xsl:with-param name="n" select="$loopCount"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="bmkName">
<xsl:call-template name="getNthValue">
<xsl:with-param name="list" select="$bmInfo"/>
<xsl:with-param name="delimiter" select="' '"/>
<xsl:with-param name="n" select="1"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="bmURL">
<xsl:call-template name="getNthValue">
<xsl:with-param name="list" select="$bmInfo"/>
<xsl:with-param name="delimiter" select="' '"/>
<xsl:with-param name="n" select="2"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="bmGUID">
<xsl:call-template name="getNthValue">
<xsl:with-param name="list" select="$bmInfo"/>
<xsl:with-param name="delimiter" select="' '"/>
<xsl:with-param name="n" select="3"/>
</xsl:call-template>
</xsl:variable>
<xsl:if test="$loopCount > 0">
<dict>
<key>ReadingListNonSync</key>
<dict>
<key>neverFetchMetadata</key>
<false/>
</dict>
<key>URIDictionary</key>
<dict>
<key>title</key>
<string>
<xsl:value-of select="$bmkName"/>
</string>
</dict>
<key>URLString</key>
<string>
<xsl:value-of select="$bmURL"/>
</string>
<key>WebBookmarkType</key>
<string>WebBookmarkTypeLeaf</string>
<key>WebBookmarkUUID</key>
<string>
<xsl:value-of select="$bmGUID"/>
</string>
</dict>
<!-- recursive call -->
<xsl:call-template name="createBmEntryFragment">
<xsl:with-param name="loopCount" select="$loopCount - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="createBmFolderFragment">
<dict>
<key>Children</key>
<array>
<xsl:call-template name="createBmEntryFragment">
<xsl:with-param name="loopCount" select="$bmCount"/>
</xsl:call-template>
<xsl:if test="$keep-existing = 'true'">
<xsl:copy-of select="./array/node()|@*"/>
</xsl:if>
</array>
<key>Title</key>
<string>
<xsl:value-of select="$bkmarks-folder"/>
</string>
<key>WebBookmarkType</key>
<string>WebBookmarkTypeList</string>
<key>WebBookmarkUUID</key>
<string>
<xsl:value-of select="$guid"/>
</string>
</dict>
</xsl:template>
<xsl:template match="dict[string[text()='BookmarksBar']]/array">
<array>
<xsl:for-each select="dict">
<xsl:choose>
<xsl:when test="string[text()=$bkmarks-folder]">
<xsl:call-template name="createBmFolderFragment"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:if test="not(./dict/string[text()=$bkmarks-folder])">
<xsl:call-template name="createBmFolderFragment"/>
</xsl:if>
</array>
</xsl:template>
</xsl:stylesheet>
Выполнение преобразования:
Для этого .xsl
требуются параметры, которые определяют свойства каждой требуемой закладки.
Сначала убедитесь, чтоBookmarks.plits
в формате XML:
plutil -convert xml1 ~/Library/Safari/Bookmarks.plist
Использование встроенного xsltproc
для применения template.xsl
к Bookmarks.plist
.
Во-первых, cd
туда, где находится template.xsl
, и выполните эту составную команду:
guid1=$(uuidgen) && guid2=$(uuidgen) && guid3=$(uuidgen) && xsltproc --novalid --stringparam bkmarks-folder "QUUX" --stringparam bkmarks "r/Android https://www.reddit.com/r/Android/ ${guid1},r/Apple https://www.reddit.com/r/Apple/ ${guid2}" --stringparam guid "$guid3" ./template.xsl - <~/Library/Safari/Bookmarks.plist > ~/Desktop/result-plist.xml
Это создаст result-plist.xml
на вашем Desktop
, содержащем новую папку закладок с именем QUUX
с двумя новыми закладками.
Давайте разберемся с каждой частью вышеизложенногоСоставная команда:
uuidgen
генерирует три UUID, которые требуются в новом Bookmarks.plist
(один для папки и один для каждой записи закладки),Мы генерируем их заранее и передаем в XSLT, потому что:
- XSLT 1.0 не поддерживает функцию генерации UUID.
xsltproc
требует XSLT 1.0
xsltproc
'* --stringparam
обозначает пользовательские аргументы следующим образом:
--stringparam bkmarks-folder <value>
- Имя папки закладок. --stringparam bkmarks <value>
- Свойства для каждой закладки.
Каждая спецификация закладки разделяется запятой (,
).Каждая строка с разделителями имеет три значения;название закладки, URL и GUID.Эти 3 значения разделены пробелом.
--stringparam guid <value>
- GUID для папки закладок.
Последние части:
./template.xsl - <~/Library/Safari/Bookmarks.plist > ~/Desktop/result-plist.xml
определить пути к;.xsl
, исходный XML и место назначения.
Чтобы оценить только что выполненное преобразование, используйте diff
для отображения различий междудва файла.Например, запустите:
diff -yb --width 200 ~/Library/Safari/Bookmarks.plist ~/Desktop/result-plist.xml | less
Затем нажмите клавишу F несколько раз, чтобы перейти к каждой странице, пока не увидите символы >
в середине двух столбцов - они указывают, гденовые узлы элементов были добавлены.Нажмите клавишу B , чтобы вернуться назад на страницу, и введите Q , чтобы выйти из diff.
Использование сценария Bash.
Теперь мы можем использовать вышеупомянутый .xsl
в скрипте Bash.
script.sh
#!/usr/bin/env bash
declare -r plist_path=~/Library/Safari/Bookmarks.plist
# ANSI/VT100 Control sequences for colored error log.
declare -r fmt_red='\x1b[31m'
declare -r fmt_norm='\x1b[0m'
declare -r fmt_green='\x1b[32m'
declare -r fmt_bg_black='\x1b[40m'
declare -r error_badge="${fmt_red}${fmt_bg_black}ERR!${fmt_norm}"
declare -r tick_symbol="${fmt_green}\\xE2\\x9C\\x94${fmt_norm}"
if [ -z "$1" ] || [ -z "$2" ]; then
echo -e "${error_badge} Missing required arguments" >&2
exit 1
fi
bkmarks_folder_name=$1
bkmarks_spec=$2
keep_existing_bkmarks=${3:-false}
# Transform bookmark spec string into array using comma `,` as delimiter.
IFS=',' read -r -a bkmarks_spec <<< "${bkmarks_spec//, /,}"
# Append UUID/GUID to each bookmark spec element.
bkmarks_spec_with_uuid=()
while read -rd ''; do
[[ $REPLY ]] && bkmarks_spec_with_uuid+=("${REPLY} $(uuidgen)")
done < <(printf '%s\0' "${bkmarks_spec[@]}")
# Transform bookmark spec array back to string using comma `,` as delimiter.
bkmarks_spec_str=$(printf '%s,' "${bkmarks_spec_with_uuid[@]}")
bkmarks_spec_str=${bkmarks_spec_str%,} # Omit trailing comma character.
# Check the .plist file exists.
if [ ! -f "$plist_path" ]; then
echo -e "${error_badge} File not found: ${plist_path}" >&2
exit 1
fi
# Verify that plist exists and contains no syntax errors.
if ! plutil -lint -s "$plist_path" >/dev/null; then
echo -e "${error_badge} Broken or missing plist: ${plist_path}" >&2
exit 1
fi
# Ignore ShellCheck errors regarding XSLT variable references in template below.
# shellcheck disable=SC2154
xslt() {
cat <<'EOX'
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output
method="xml"
indent="yes"
doctype-system="http://www.apple.com/DTDs/PropertyList-1.0.dtd"
doctype-public="-//Apple//DTD PLIST 1.0//EN"/>
<xsl:param name="bkmarks-folder"/>
<xsl:param name="bkmarks"/>
<xsl:param name="guid"/>
<xsl:param name="keep-existing" select="false" />
<xsl:variable name="bmCount">
<xsl:value-of select="string-length($bkmarks) -
string-length(translate($bkmarks, ',', '')) + 1"/>
</xsl:variable>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template name="getNthValue">
<xsl:param name="list"/>
<xsl:param name="n"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="$n = 1">
<xsl:value-of select=
"substring-before(concat($list, $delimiter), $delimiter)"/>
</xsl:when>
<xsl:when test="contains($list, $delimiter) and $n > 1">
<!-- recursive call -->
<xsl:call-template name="getNthValue">
<xsl:with-param name="list"
select="substring-after($list, $delimiter)"/>
<xsl:with-param name="n" select="$n - 1"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="createBmEntryFragment">
<xsl:param name="loopCount" select="1"/>
<xsl:variable name="bmInfo">
<xsl:call-template name="getNthValue">
<xsl:with-param name="list" select="$bkmarks"/>
<xsl:with-param name="delimiter" select="','"/>
<xsl:with-param name="n" select="$loopCount"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="bmkName">
<xsl:call-template name="getNthValue">
<xsl:with-param name="list" select="$bmInfo"/>
<xsl:with-param name="delimiter" select="' '"/>
<xsl:with-param name="n" select="1"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="bmURL">
<xsl:call-template name="getNthValue">
<xsl:with-param name="list" select="$bmInfo"/>
<xsl:with-param name="delimiter" select="' '"/>
<xsl:with-param name="n" select="2"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="bmGUID">
<xsl:call-template name="getNthValue">
<xsl:with-param name="list" select="$bmInfo"/>
<xsl:with-param name="delimiter" select="' '"/>
<xsl:with-param name="n" select="3"/>
</xsl:call-template>
</xsl:variable>
<xsl:if test="$loopCount > 0">
<dict>
<key>ReadingListNonSync</key>
<dict>
<key>neverFetchMetadata</key>
<false/>
</dict>
<key>URIDictionary</key>
<dict>
<key>title</key>
<string>
<xsl:value-of select="$bmkName"/>
</string>
</dict>
<key>URLString</key>
<string>
<xsl:value-of select="$bmURL"/>
</string>
<key>WebBookmarkType</key>
<string>WebBookmarkTypeLeaf</string>
<key>WebBookmarkUUID</key>
<string>
<xsl:value-of select="$bmGUID"/>
</string>
</dict>
<!-- recursive call -->
<xsl:call-template name="createBmEntryFragment">
<xsl:with-param name="loopCount" select="$loopCount - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="createBmFolderFragment">
<dict>
<key>Children</key>
<array>
<xsl:call-template name="createBmEntryFragment">
<xsl:with-param name="loopCount" select="$bmCount"/>
</xsl:call-template>
<xsl:if test="$keep-existing = 'true'">
<xsl:copy-of select="./array/node()|@*"/>
</xsl:if>
</array>
<key>Title</key>
<string>
<xsl:value-of select="$bkmarks-folder"/>
</string>
<key>WebBookmarkType</key>
<string>WebBookmarkTypeList</string>
<key>WebBookmarkUUID</key>
<string>
<xsl:value-of select="$guid"/>
</string>
</dict>
</xsl:template>
<xsl:template match="dict[string[text()='BookmarksBar']]/array">
<array>
<xsl:for-each select="dict">
<xsl:choose>
<xsl:when test="string[text()=$bkmarks-folder]">
<xsl:call-template name="createBmFolderFragment"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:if test="not(./dict/string[text()=$bkmarks-folder])">
<xsl:call-template name="createBmFolderFragment"/>
</xsl:if>
</array>
</xsl:template>
</xsl:stylesheet>
EOX
}
# Convert the .plist to XML format
plutil -convert xml1 -- "$plist_path" >/dev/null || {
echo -e "${error_badge} Cannot convert .plist to xml format" >&2
exit 1
}
# Generate a UUID/GUID for the folder.
folder_guid=$(uuidgen)
xsltproc --novalid \
--stringparam keep-existing "$keep_existing_bkmarks" \
--stringparam bkmarks-folder "$bkmarks_folder_name" \
--stringparam bkmarks "$bkmarks_spec_str" \
--stringparam guid "$folder_guid" \
<(xslt) - <"$plist_path" > "${TMPDIR}result-plist.xml"
# Convert the .plist to binary format
plutil -convert binary1 -- "${TMPDIR}result-plist.xml" >/dev/null || {
echo -e "${error_badge} Cannot convert .plist to binary format" >&2
exit 1
}
mv -- "${TMPDIR}result-plist.xml" "$plist_path" 2>/dev/null || {
echo -e "${error_badge} Cannot move .plist from TMPDIR to ${plist_path}" >&2
exit 1
}
echo -e "${tick_symbol} Successfully created Safari bookmarks."
Объяснение
script.sh
предоставляет следующие функции:
- Упрощенный API, который будет полезен при выполнении через Python.
- Проверяет, что
.plist
не сломан.
- Обработка ошибок / ведение журнала.
- Преобразует
.plist
через xsltproc
, используя template.xsl
inlined.
- Создает GUID для передачи в XSLT на основании номера. закладок, указанных в приведенных аргументах.
- Преобразует
.plist
в XML и обратно в двоичный файл.
- Записывает новый файл в папку ОС temp , затем перемещает его в каталог
Bookmarks.plist
, эффективно заменяя оригинальный.
Запуск сценария оболочки
cd
, где находится script.sh
, и выполните следующую команду chmod
, чтобы сделать script.sh
исполняемым:
chmod +ux script.sh
Запустите следующую команду:
./script.sh "stackOverflow" "bash https://stackoverflow.com/questions/tagged/bash,python https://stackoverflow.com/questions/tagged/python"
Затем в CLI выводится следующее:
✔ Successfully created Safari bookmarks.
Safari теперь имеет папку закладок с именем stackOverflow
, содержащую две закладки (bash
и python
).
Использование скрипта Python
Есть несколько способов выполнить script.sh
через файл .py
.
Метод A: скрипт внешней оболочки
Следующий файл .py
выполняет внешний файл script.sh
. Давайте назовем файл create-safari-bookmarks.py
и сохраним его в той же папке, что и script.sh
.
create-safari-bookmarks.py
#!/usr/bin/env python
import subprocess
def run_script(folder_name, bkmarks):
subprocess.call(["./script.sh", folder_name, bkmarks])
def tuple_to_shell_arg(tup):
return ",".join("%s %s" % t for t in tup)
reddit_bkmarks = [
('r/Android', 'https://www.reddit.com/r/Android/'),
('r/Apple', 'https://www.reddit.com/r/Apple/'),
('r/Mac', 'https://www.reddit.com/r/Mac/'),
('r/ProgrammerHumor', 'https://www.reddit.com/r/ProgrammerHumor/'),
('r/gaming', 'https://www.reddit.com/r/gaming/')
]
so_bkmarks = [
('bash', 'https://stackoverflow.com/questions/tagged/bash'),
('python', 'https://stackoverflow.com/questions/tagged/python'),
('xslt', 'https://stackoverflow.com/questions/tagged/xslt'),
('xml', 'https://stackoverflow.com/questions/tagged/xml')
]
run_script('subreddit', tuple_to_shell_arg(reddit_bkmarks))
run_script("stackOverflow", tuple_to_shell_arg(so_bkmarks))
Пояснение:
Первый оператор def
определяет функцию run-script
. Имеет два параметра; folder_name
и bkmarks
. Метод subprocess
modules call
по существу выполняет script.sh
с необходимыми аргументами.
Второй оператор def
определяет функцию tuple_to_shell_arg
. Он имеет один параметр tup
. Метод String join()
преобразует список кортежей в формат, необходимый для script.sh
. Это существенно преобразует список кортежей, таких как:
[
('foo', 'https://www.foo.com/'),
('quux', 'https://www.quux.com')
]
и возвращает строку:
foo https://www.foo.com/,quux https://www.quux.com
Функция run_script
вызывается следующим образом:
run_script('subreddit', tuple_to_shell_arg(reddit_bkmarks))
Это передает два аргумента; subreddit
(имя папки с закладками) и спецификация для каждой требуемой закладки (отформатированной, как описано ранее в пункте № 2).
Бег create-safari-bookmarks.py
Сделать create-safari-bookmarks.py
исполняемым:
chmod +ux ./create-safari-bookmarks.py
Затем вызовите его с помощью:
./create-safari-bookmarks.py
Метод B: встроенный скрипт оболочки
В зависимости от вашего конкретного случая использования, вы можете рассмотреть встраивание script.sh
в файл .py
вместо вызова внешнего файла .sh
. Давайте назовем этот файл create-safari-bookmarks-inlined.py
и сохраним его в том же каталоге, где находится create-safari-bookmarks.py
.
Важно:
Вам необходимо скопировать и вставить все содержимое из script.sh
в create-safari-bookmarks-inlined.py
, где указано.
Вставьте его в следующую строку после части bash_script = """\
.
- Часть
"""
в create-safari-bookmarks-inlined.py
должна находиться на отдельной строке после последней строки вставленного содержимого script.sh
.
Строка 31 из script.sh
при встраивании в .py
должна содержать часть '%s\0'
(\0
- нулевой символ) с другой обратной косой чертой, т.е. строка 31 из script.sh
должна выглядеть следующим образом :
...
done < <(printf '%s\\0' "${bkmarks_spec[@]}")
^
...
Эта строка, вероятно, будет в строке 37 в create-safari-bookmarks-inlined.py
.
create-safari-bookmarks-inlined.py
#!/usr/bin/env python
import tempfile
import subprocess
bash_script = """\
# <--- Copy and paste content of `script.sh` here and modify its line 31.
"""
def run_script(script, folder_name, bkmarks):
with tempfile.NamedTemporaryFile() as scriptfile:
scriptfile.write(script)
scriptfile.flush()
subprocess.call(["/bin/bash", scriptfile.name, folder_name, bkmarks])
def tuple_to_shell_arg(tup):
return ",".join("%s %s" % t for t in tup)
reddit_bkmarks = [
('r/Android', 'https://www.reddit.com/r/Android/'),
('r/Apple', 'https://www.reddit.com/r/Apple/'),
('r/Mac', 'https://www.reddit.com/r/Mac/'),
('r/ProgrammerHumor', 'https://www.reddit.com/r/ProgrammerHumor/'),
('r/gaming', 'https://www.reddit.com/r/gaming/')
]
so_bkmarks = [
('bash', 'https://stackoverflow.com/questions/tagged/bash'),
('python', 'https://stackoverflow.com/questions/tagged/python'),
('xslt', 'https://stackoverflow.com/questions/tagged/xslt'),
('xml', 'https://stackoverflow.com/questions/tagged/xml')
]
run_script(bash_script, "subreddit", tuple_to_shell_arg(reddit_bkmarks))
run_script(bash_script, "stackOverflow", tuple_to_shell_arg(so_bkmarks))
Объяснение
Этот файл достигает того же результата, что и create-safari-bookmarks.py
.
Этот модифицированный сценарий .py
включает в себя модифицированную функцию run_script
, которая использует модуль Python tempfile
для сохранения встроенного сценария оболочки во временный файл.
Метод Python subprocess
modules call
затем выполняет временный созданный файл оболочки.
Бег create-safari-bookmarks-inlined.py
Сделать create-safari-bookmarks-inlined.py
исполняемым:
chmod +ux ./create-safari-bookmarks-inlined.py
Затем вызовите его, запустив:
./create-safari-bookmarks-inlined.py
Дополнительное примечание: добавление закладок в существующую папку
В настоящее время каждый раз, когда вышеупомянутые сценарии / команды запускаются снова, мы фактически заменяем любую существующую именную папку закладок Safari (которая имеет то же имя, что и заданное имя папки закладок) на совершенно новую и создаем указанные закладки. .
Однако, если вы хотите добавить закладки в выходящую папку, тогда template.xsl
включает в себя один дополнительный параметр / аргумент, который будет передан ей. Обратите внимание на часть в строке 14, которая гласит:
<xsl:param name="keep-existing" select="false" />
Это значение по умолчанию false
. Итак, если мы изменим функцию run_script
, скажем, create-safari-bookmarks.py
на следующую.
def run_script(folder_name, bkmarks, keep_existing):
subprocess.call(["./script.sh", folder_name, bkmarks, keep_existing])
То есть добавить третий параметр с именем keep_existing
и включить ссылку на него в subprocess.call([...])
, то есть, чтобы он передавался в качестве третьего аргумента script.sh
(... и впоследствии в XSLT таблицы стилей).
Затем мы можем вызвать функцию run_script
и передать дополнительный аргумент String, либо "true"
, либо "false"
, например:
run_script('subreddit', tuple_to_shell_arg(reddit_bkmarks), "true")
run_script("stackOverflow", tuple_to_shell_arg(so_bkmarks), "false")
Однако внесение вышеуказанных изменений (то есть передача "true"
для сохранения существующих закладок) может привести к созданию дублирующих закладок. Например; дубликаты закладок появятся, когда у нас будет существующая закладка (имя и URL), которая затем будет переиздана с тем же именем и URL позже.
Ограничения: В настоящее время любой аргумент имени, предоставленный для закладки, не может содержать пробел (ы), потому что они используются в качестве разделителей сценариями.
Ограничения MacOS Mojave
Из-за более строгих политик безопасности в macOS Mojave (10.14.x) доступ к ~/Library/Safari/Bookmarks.plist
по умолчанию не разрешен (как указано в в этом ответе ).
Следовательно, необходимо предоставить Terminal.app (или другому предпочтительному инструменту CLI, например iTerm ) доступ ко всему диску. Для этого вам необходимо:
- Выберите Системные настройки в меню Apple.
- В окне Системные настройки щелкните значок Безопасность и политика .
- На панели Безопасность и политика перейдите на вкладку Конфиденциальность .
- Выберите Полный доступ к диску в левой колонке.
- Нажмите значок замка в нижнем левом углу, чтобы разрешить изменения.
- Введите пароль администратора, затем нажмите кнопку Разблокировать .
- Далее щелкните значок плюса ( + ).
- Выберите Terminal.app , который может быть расположен в
/Applications/Utilities/
, затем нажмите кнопку Открыть .
- Terminal.app будет добавлено в список.
- Щелкните значок блокировки, чтобы предотвратить дальнейшие изменения, и выйдите из системы Системные настройки .