Remove unused tags?

My longer used libraries seem to collect large number of tags listed in sidebar, which only few may be still used. Is there any way to identify and delete the tags which are no more used? There is Apple script from 2013 in one of the contributions here, but that seems to be asking for Growl, which is retired by now.

I’ve updated the script to use EagleFiler’s built-in notification feature instead of Growl. However, when I tested the script on one of my libraries it incorrectly identified lots of tags as being unused. I’ve not used the script before and haven’t had time to look into what the problem might be. But I’m posting the work in progress in case you find this helpful.

-- identify-unused-tags.scpt v. 04
-- Purpose: identify tags not assigned to any record

-- Usage notes:
--    CAUTION: E-mail messages in mailboxes are not currently available via AppleScript. So the script will find, as 'unused', tags that are not used by files (including messages outside of mailboxes) but which -are- used by messages in mailboxes. [per Michael Tsai] If the library contains a mailbox (empty or not), the last "Growl" notification reminds you to check the unused tags for mail messages.
--    Unused tags do not include 1) built-in EF tags and 2) user-specified "permanent" tags. User-specified permanent tags start with "!_"; other customizations are possible.)
--    The script operates on -all- library records -- i.e., record selection is ignored.
--    Do not change or close front library until script completes. (You can switch to other applications.)
--    For Growl 1.x, adjust Growl test as indicated below.
--    After running the script, check each "unused" tag to ensure it is empty.

-- Author: humanengr
-- Available from: the EagleFiler forum (
-- Last Modified: 2013-03-23

-- Version history
-- v. 04 
--    Michael removed Growl dependency.
-- v. 03
--    Added code for running w/o Growl.
-- v. 02
--    Addressed a garbage collection issue. (Some unused tags were overlooked in older libraries.) 
-- v. 01
--    Fixed mistaken classification as "unused" of empty parent tag with -multiple- used child tags.
--    Fixed case sensitivity -- unused tags with different case than a used tag are now classified as "unused".
-- v. 00

tell application "EagleFiler"
    notify with name "Progress" title "Getting list of all the tags" description "in the library's list of tags"
    considering case
        -- Get names of document tags
        set _documentTags to front document's tags
        set _documentTagNames to {}
        repeat with _tag in _documentTags
            set end of _documentTagNames to (name of _tag)
        end repeat
        notify with name "Progress" title "Collecting tags used in records ..." description "(this might take a minute or two)"
        -- Collect tags used in all library records
        -- Set _records to every library record of document 1
        --    and check if there are any mailboxes in the library
        set _records to every library record of front document
        set _usedTags to {}
        set _usedTagNames to {}
        repeat with _record in _records
            if id of _record is not 2 then -- record is not "Trash"
                set _usedTags to _usedTags & assigned tags of _record
                set _usedTagNames to _usedTagNames & assigned tag names of _record
            end if
        end repeat
        -- Include the entire parental hierarchy of any used tags   
        --   Initialize
        set _tagsToTestForParents to _usedTags -- Begin by testing all used tags
        set _testForNewParents to "yes"
        set _newParents to {}
        set _newParentsNames to {}
        --    Move up the hierarchy for each used tag
        repeat until _testForNewParents is "no"
            -- Test each used tag to see if it has a tag parent
            repeat with _tag in _tagsToTestForParents
                if (_tag's container is not missing value and _usedTagNames does not contain (name of _tag's container) as text) then
                    -- Collect parents
                    if name of _tag's container is not in _newParentsNames then
                        set end of _newParents to _tag's container
                        set _newParentsNames to _newParentsNames & (name of _tag's container) as text
                    end if
                end if
            end repeat
            -- Add parents to used tags
            if (count of _newParents) > 0 then
                set end of _usedTags to _newParents
                set end of _usedTagNames to _newParentsNames
            end if
            -- Reset set of tags to test to the newly discovered parents
            set _tagsToTestForParents to _newParents
            -- If no new parents were discovered, end the loop
            if (count of _newParents) is 0 then set _testForNewParents to "no"
            -- Reinitialize
            set _newParents to {}
            set _newParentsNames to ""
        end repeat
        notify with name "Progress" title "Removing dupes, sorting, and" description "identifying unused tags"
        -- Remove dupes and sort the list
        set _usedTagNames to my DeDupeAndSort(_usedTagNames)
        -- Compare lists to create list of unused tags
        set _unusedTags to {}
        repeat with _TagName in _documentTagNames
            if _TagName is not in _usedTagNames then
                set _unusedTags to (_unusedTags & _TagName)
            end if
        end repeat
        -- Ignore permanent EF tags
        set _permanentTags to {"note", "__deleted__", "flagged", "unread"}
        -- Ignore "permanent" user-specified tags -- assumed to start with "!_"
        set _tagsToDelete to {}
        repeat with i from 1 to count _unusedTags
            if ({_unusedTags's item i} is not in _permanentTags and _unusedTags's item i does not contain "!_") then set _tagsToDelete's end to _unusedTags's item i
        end repeat
        -- Alternate code for "permanent" user-specified tags
        -- set _permanentTags to _permanentTags & {"PermanentTag1", "PermanentTag2", …}
        -- Create an "unused" tag to contain the tags to delete
        tell front document
            set _countUnused to 0
            repeat with _name in _tagsToDelete
                if _name is not "unused" then
                    set _tag to tag _name
                    repeat with _subtag in _tag's tags
                        -- Don't want to get rid of subtags that might be in use
                        set _subtag's container to _tag's container
                    end repeat
                    set _tag's container to tag "unused"
                    set _countUnused to _countUnused + 1
                end if
            end repeat
        end tell
        tell front document
            if exists (library records whose universal type identifier is "com.c-command.mbox") then
                set _description to "Check tags for mail messages"
                set _description to ""
            end if
        end tell
        if _countUnused is 0 then
            set _title to "No unused tags found"
            set _description to ""
            set _title to (_countUnused as text) & " unused tags found"
        end if
        notify with name "Done" title _title description _description
    end considering
end tell

-- DJ Bazzie Wazzie,
on DeDupeAndSort(theList)
    set AppleScript's text item delimiters to {ASCII character 10}
    set theString to theList as string
    set AppleScript's text item delimiters to ""
    set sortedList to paragraphs of (do shell script "/bin/echo " & quoted form of theString & "| sort -fu") -- removed -n (do not print trailing newline character) from echo; added -f (ignore case) to sort.
end DeDupeAndSort

The script seems to find the tag uses by looping over all the records and making a list of the used tag names. I would suggest a different approach of asking EagleFiler how many records use each tag. And that should be a lot faster so that you don’t need the progress notifications. Here’s a proof of concept:

tell application "EagleFiler"
    tell library document 1
        set _tags to tags
        repeat with _tag in _tags
            if (count of _tag's library records) is 0 then
                display dialog "Tag “" & _tag's name & "” seems to be unused (except possibly by messages within mailboxes)."
            end if
        end repeat
    end tell
end tell

This could be adapted to move the tags to an unused parent, as in the original script.

Hello Michael,
yes, this seems to work just fine for me. Thank you for your help!

tell application "EagleFiler"
	tell library document 1
		set _tags to tags
		repeat with _tag in _tags
			if (count of _tag's library records) is 0 then
				#display dialog "Tag “" & _tag's name & "” seems to be unused (except possibly by messages within mailboxes)."
				set _tag's container to tag "unused"
			end if
		end repeat
	end tell
end tell

Great! If, for some reason, it moves a tag that you didn’t want (e.g. because it was used by messages in a mailbox) you can Undo (perhaps multiple times), and the tags should move back to their original locations.