Как программно создавать закладки MacOS Safari и управлять ими? - PullRequest
2 голосов
/ 09 июня 2019

Я делаю скрипт, который обновляет закладки в моем MacOS Safari, чтобы все мои подписанные субредакты всегда были отдельными закладками в определенной папке.Я дошел до того, что у меня есть все subreddits в виде отсортированного списка кортежей в Python с желаемым именем закладки в качестве первого элемента и URL закладки в качестве второго элемента:

bookmarks = [
     ('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/')
]

КакМогу ли я очистить свою папку закладок subreddit в Safari и создать эти новые закладки в этой папке?

До этого момента я использовал Python, но вызов внешнего сценария AppleScript или Shell из программы Python не будет проблемой.

Вот изображение желаемого результата, каждая закладка связана с соответствующим URL-адресом subreddit:

Bookmarks folder

Ответы [ 2 ]

1 голос
/ 19 июня 2019

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 требуются параметры, которые определяют свойства каждой требуемой закладки.

  1. Сначала убедитесь, чтоBookmarks.plits в формате XML:

    plutil -convert xml1 ~/Library/Safari/Bookmarks.plist
    
  2. Использование встроенного 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 с двумя новыми закладками.

  3. Давайте разберемся с каждой частью вышеизложенногоСоставная команда:

    • 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 и место назначения.

  4. Чтобы оценить только что выполненное преобразование, используйте 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 предоставляет следующие функции:

  1. Упрощенный API, который будет полезен при выполнении через Python.
  2. Проверяет, что .plist не сломан.
  3. Обработка ошибок / ведение журнала.
  4. Преобразует .plist через xsltproc, используя template.xsl inlined.
  5. Создает GUID для передачи в XSLT на основании номера. закладок, указанных в приведенных аргументах.
  6. Преобразует .plist в XML и обратно в двоичный файл.
  7. Записывает новый файл в папку ОС temp , затем перемещает его в каталог Bookmarks.plist, эффективно заменяя оригинальный.

Запуск сценария оболочки

  1. cd, где находится script.sh, и выполните следующую команду chmod, чтобы сделать script.sh исполняемым:

    chmod +ux script.sh
    
  2. Запустите следующую команду:

    ./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))

Пояснение:

  1. Первый оператор def определяет функцию run-script. Имеет два параметра; folder_name и bkmarks. Метод subprocess modules call по существу выполняет script.sh с необходимыми аргументами.

  2. Второй оператор 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
    
  3. Функция run_script вызывается следующим образом:

    run_script('subreddit', tuple_to_shell_arg(reddit_bkmarks))
    

    Это передает два аргумента; subreddit (имя папки с закладками) и спецификация для каждой требуемой закладки (отформатированной, как описано ранее в пункте № 2).

Бег create-safari-bookmarks.py

  1. Сделать create-safari-bookmarks.py исполняемым:

    chmod +ux ./create-safari-bookmarks.py
    
  2. Затем вызовите его с помощью:

    ./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))

Объяснение

  1. Этот файл достигает того же результата, что и create-safari-bookmarks.py.

  2. Этот модифицированный сценарий .py включает в себя модифицированную функцию run_script, которая использует модуль Python tempfile для сохранения встроенного сценария оболочки во временный файл.

  3. Метод Python subprocess modules call затем выполняет временный созданный файл оболочки.

Бег create-safari-bookmarks-inlined.py

  1. Сделать create-safari-bookmarks-inlined.py исполняемым:

    chmod +ux ./create-safari-bookmarks-inlined.py
    
  2. Затем вызовите его, запустив:

    ./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 ) доступ ко всему диску. Для этого вам необходимо:

  1. Выберите Системные настройки в меню Apple.
  2. В окне Системные настройки щелкните значок Безопасность и политика .
  3. На панели Безопасность и политика перейдите на вкладку Конфиденциальность .
  4. Выберите Полный доступ к диску в левой колонке.
  5. Нажмите значок замка в нижнем левом углу, чтобы разрешить изменения.
  6. Введите пароль администратора, затем нажмите кнопку Разблокировать .
  7. Далее щелкните значок плюса ( + ).
  8. Выберите Terminal.app , который может быть расположен в /Applications/Utilities/, затем нажмите кнопку Открыть .
  9. Terminal.app будет добавлено в список.
  10. Щелкните значок блокировки, чтобы предотвратить дальнейшие изменения, и выйдите из системы Системные настройки .

screenshot

1 голос
/ 10 июня 2019

Я никогда не находил команды AS для управления закладками в Safari (не в словаре AS).Поэтому я построил свои собственные подпрограммы, чтобы поиграть с листом закладок Safari.Однако они подвержены неожиданным изменениям, внесенным Apple в способ обработки закладок в будущем!до сих пор он все еще работает, но я пока не использую 10.14

Сначала вы должны получить этот plist-файл, чтобы изменить его.Эта часть должна быть в вашем основном коде.он дает вам патч к вашему plist-файлу:

 set D_Lib to ((path to library folder from user domain) as string) & "Safari"
 set SafariPlistFile to D_Lib & ":Bookmarks.plist"

Вот 2 подпрограммы для управления закладками.Первый проверяет, существует ли закладка

on Exist_BM(FPlist, BM_Name) -- Search bookmark named BM_Name in Plist file. returns number or 0 if not found. This search is limited to main bar, not sub menus
    tell application "System Events"
        set numBM to 0
        set Main_Bar to property list item "Children" of property list item 2 of property list item "Children" of property list file FPlist
        tell Main_Bar
            set myBM to every property list item of Main_Bar
            repeat with I from 1 to (count of myBM)
                set myType to value of property list item "WebBookmarkType" of (item I of myBM)
                if (myType = "WebBookmarkTypeLeaf") then
                    if (value of property list item "title" of property list item "URIDictionary" of (item I of myBM)) = BM_Name then
                        set numBM to I
                        exit repeat
                    end if
                end if
            end repeat
        end tell
    end tell
    return numBM
end Exist_BM

Вы можете назвать этот обработчик следующим образом:

Set myAndroid to  Exist_BM(SafariPlistFile,"r/Android")
if myAndroid >0 then -- set here the code to update : the bookmark already exists
        else -- set here the code to add new bookmark "r/Android"
        end if

Второй обработчик создает новую закладку:

on New_BM(FPlist, BM_Name, N_URL) -- create new bookmark at right end side of bookmarks and return its number
    tell application "System Events"
        set Main_Bar to property list item "Children" of property list item 2 of property list item "Children" of property list file FPlist
        set numBM to count of property list item of Main_Bar
        tell Main_Bar
            set my_UUID to do shell script "uuidgen" -- create unique Apple UID
            set myNewBM to make new property list item at the end with properties {kind:record}
            tell myNewBM
                set URIDict to make new property list item with properties {kind:record, name:"URIDictionary"}
                tell URIDict to make new property list item with properties {name:"title", kind:string, value:BM_Name}
                make new property list item with properties {name:"URLString", kind:string, value:N_URL}
                make new property list item with properties {name:"WebBookmarkType", kind:string, value:"WebBookmarkTypeLeaf"}
                make new property list item with properties {name:"WebBookmarkUUID", kind:string, value:my_UUID}
            end tell -- myNewBM
        end tell
    end tell
    return (numBM + 1)
end New_BM

Я использовал эти процедуры для добавления, проверки и изменения закладки в правой части моих закладок.В вашем случае вам нужно поиграть с подменю закладок, а затем вы должны настроить этот код, но основная концепция та же.

Чтобы упростить процесс, я рекомендую вам начать поиск вашего plist-файла.(Библиотека / Safari / Bookmarks.plist), чтобы увидеть его структуру, когда у вас есть закладки в подменю.

Надеюсь, это поможет!

...