Skip to content

Instantly share code, notes, and snippets.

@PaulSolt
Last active July 13, 2018 13:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PaulSolt/0d0ee9ee385ab5c42358b3ee44bcd25b to your computer and use it in GitHub Desktop.
Save PaulSolt/0d0ee9ee385ab5c42358b3ee44bcd25b to your computer and use it in GitHub Desktop.
Sample code to extract the table of contents and output in-line for a blog post on Ghost (copy/paste, buggy output currently)
//: Table of contents generator for Markdown using {{TOC}} for text replacement and link insertion
//: Fixed the bug with {{TOC}} on second pass, added some more logic to prevent losing whitespace, and making
//: non-unicode links from the TOC (good start prototyping and working with strings, now I need to cleanup and do TDD + Mac UI)
//: I plan to wrap it up more and fix the bugs, so that I can use it as a Mac app, but this was my starting point
//: with lots of todos
//: Paul Solt (Swift 4.2 in Xcode 10)
//: 7-12-18
import Cocoa
// TODO: Verify with a test that this matches when we iterate through the string using this approach
// TODO: We don't want to transform the text if it's mac/windows newlines
var input2 = """
{{TOC}}
# Title
ONE two three
{{TOC}}
## Overview
{{TOC}}
## More things
* THing 1
* Thing 2
* Thing 3
## Additional notes
ROck on!
## closing thoughts
Do it!
"""
var input = """
Do you struggle to finish your tasks? (Are you mentally drained after working all day?) Download one of these 6 Pomodoro timers, so that you can enjoy more energy, focus, and drive!
The Pomodoro method can help you complete todos, write faster, and do more—without the typical distractions that prevent you from getting things done (GTD).
## Quick Links
{{TOC}}
## What is the Pomodoro method?
The Pomodoro method is a way to have more energy, more focus, and work faster throughout the day.
The [Pomodoro Technique was created by Francesco Cirillo](https://francescocirillo.com/pages/pomodoro-technique) in the 1980's as a way to boost productivity in highly creative fields of work.
In essence it's a productivity hack that harnesses the power of getting things done by eliminating distractions and adding accountability.
Download one of these 6 Mac apps to get started, and learn how you can easily apply the technique to 3x your creative output.
## 6 Timer Apps on Mac (Pomodoro Friendly Apps)
Finding a good Mac timer app is hard. Many on the App Store are out of date, buggy, crash, or are difficult to use.
Here are 6 different countdown timer apps that you can use on macOS High Sierra and Mojave.
### 1. Super Easy Timer (4.3 stars: ★★★★☆)
If you want a simple timer, that is easy to use, you download [Super Easy Timer for Mac](https://itunes.apple.com/us/app/super-easy-timer/id1353137878?mt=12).
[![Super-Easy-Timer-Hero](/content/images/2018/04/Super-Easy-Timer-Hero.gif)](https://itunes.apple.com/us/app/super-easy-timer/id1353137878?mt=12)
1. You can quickly restart a previous timer
2. You can use natural language to create timers by typing "25" or "25 minutes"
3. You can minimize the timer so it's less distracting
**Download Now**
[Download Super Easy Timer from the Mac App Store](https://itunes.apple.com/us/app/super-easy-timer/id1353137878?mt=12) and start your first Pomodoro session today.
**Free 7-Day Trial**
*You can try the app for free to see if it works for you. *Try before you buy: send the 7-Day Free Trial directly to your Inbox.
<script async id="_ck_298469" src="https://forms.convertkit.com/298469?v=7"></script>
Super Easy Timer supports full-screen, the menu bar, mini-timers, and you can even minimize it to your dock (I used it to write this article).
### 2. Tomato One (★★★★☆)
[Tomato One](https://itunes.apple.com/us/app/tomato-one-free-focus-timer/id907364780?mt=12) (Free with ads, Disable ads for $1.99)
Minimal pomodoro timer app with Menubar support for starting, taking breaks, and tracking sessions
### 3. BreakTime (★★★★☆)
[BreakTime](https://itunes.apple.com/us/app/breaktime/id427475982?mt=12) ($4.99)
Need something to lock your computer after 25 minutes? Try this app.
### 4. Good Timer (★★★★★)
[Good Timer](https://itunes.apple.com/us/app/good-timer/id1268457877?mt=12) ($0.99)
Another minimal timer that you can customize
### 5. Be Focused (★★★★★)
A more full featured pomodoro timer, but it's user interface is more complex (hard to use)
[Be Focused](https://itunes.apple.com/us/app/be-focused-focus-timer/id973134470?mt=12) (Free trial, $4.99 for full version)
### 6. Red Hot Timer (4.5 stars: ★★★★☆)
[Red Hot Timer](https://itunes.apple.com/us/app/red-hot-timer/id929960914?mt=12) (Free trial, $4.99 for full version)
The timer that inspired me to create Super Easy Timer, somewhat buggy, and a little clunky.
## Usability and Productivity
There are things I like in each of these timer apps, but none of them work like I wanted, which is why I created [Super Easy Timer](https://itunes.apple.com/us/app/super-easy-timer/id1353137878?mt=12) ([try the 7-day trial for free](http://SuperEasyTimer.com).
## How to Do the Pomodoro Method
1. Pick a task and break it down into chunks of work
2. Set a timer for 25 minutes
2. Work uninterrupted for as long as the timer is going
3. Mark that you've completed one unit of work (tally on a calendar)
4. Take a 5 minute break (Every 4, take a 20-30 minute break)
5. Restart with a new task
## Why the Pomodoro Method Works?
Doing tasks with total focus allows you to do deeper work (Read Cal Newports book: [Deep Work](https://amzn.to/2Jq2tiy)). The timer is your accountability partner and keeps you focused and on track.
The challenging part of information work is that tasks are not always well defined, and they tend to take longer than we expect.
This can be frustrating for someone who just wants to finish a chunk of code, write the paper, or publish a new website.
The Pomodoro gives you a metric to use as you work on these creative endeavors, and it gives you a sense of accomplishment for a large task that might take weeks to complete.
The sense of accomplishment is the reason that you feel good, because you marked down a unit of work, and you got something done, even if it's just one small part of the big problem.
## Break Up Large Tasks into Easy Pomodoros
The breaks should not be looked at as interruptions, but instead as a way to recharge and activate your subconscious mind to continue working on the problem.
While you take a physical break from your work, your mind will continue to work on the problem for you, doing the heavy lifting, so that you can work faster and more effectively.
It's ok even if you don't finish a task within the 25 minutes.
**Why?**
Because your mind hates open loops, and it'll keep thinking about the problem, giving you new insight, creating ideas, trying to finish the work when you're not actively thinking about the problem.
Most creative people make their discoveries away from the work bench, when they're on a walk, taking a nap, or relaxing.
## The One Rule
When the timer is going you have a rule.
**You don't stop working if the timer is active.** (unless it's a real emergency).
While the timer is going you don't check email . . .
. . . you don't respond to texts
. . . you don't get into a discussion with co-workers
. . . you don't refill your coffee cup
. . . you don't talk to your boss
. . . you don't do anything that isn't the one task that you decided to work on when you started the work session.
## Prepping for Your Next Pomodoro
Starting the Pomodoro method can be a little awkward at work or around your family who are used to having you always available.
Before you start, you'll want to tell your co-workers, your boss, or your family that you'll be busy for the next 25 minutes.
When you're done your work session, you can chat with them briefly during your 5 min break. If you need to, bribe your kids to be quiet until you're timer dings.
## Your Energy Levels Are Higher With More Breaks
You can burn out your energy if you just work straight for 4 hours.
If you don't get out of your chair, your body is going to be fatigued from holding your posture, from not moving.
Your joints will be stiff, and your eyes will be tired.
Any creative task that requires mental energy is going to be taxing as well.
After a long day of non-stop coding I can feel pretty drained.
## Every Tally is a Victory
Instead of being bogged down with not making progress on a specific goal. Reward yourself with completing units of work: Pomodoro sessions.
It's more accomplishing to feel that you've done the work when you've crossed off 10 tally's on your calendar, than it is to say I've finished task X.
The problem with finishing task X, is that it might require tasks A, B, C, D, E, and F.
. . . and if you don't complete all those tasks in one day, then finishing task X is a failure . . .
. . . failure is not a good feeling.
Instead flip it around.
Feel accomplished because you completed the subtasks, even if that meant it took longer than you expected.
What should you do on your breaks?
## Ideas for Effective 5 Minute Breaks
1. Stand up and stretch
2. Do some yoga poses (downward dog or touch your toes and hang for 30 seconds)
3. Look at something 20 feet away for 20 seconds to reduce eye strain
4. Blink 60-90 times over 1-2 minutes to prevent dry eyes
5. Roll your shoulders and try to touch your elbows together to relax your shoulders
6. Take a walk around your parking lot, trail, or side walk
7. Go to the bathroom
8. Read a 3-5 pages from a book
9. Eat a fruit or vegetable snack
10. Refill your water or coffee (just dont' get sucked into a conversation)
11. Do squats
## Ideas for Effective 20 Minute Breaks
1. Go for a 20-minute walk outside (at noon to maximize your vitamin D)
2. Read a book in your favorite chair
3. Eat your lunch outside
4. Do a 20-minute yoga routine
5. Meditate for 20 minutes
6. Write about a different topic
## Follow Me
Did you download any of the Mac apps? Have you done a pomodoro session yet?
Let me know below with a comment and [follow me on Twitter.](http://twiter.com/PaulSolt).
"""
func createTableOfContentsFromMarkdown(input: String) -> String {
// TODO: Blog post on spliting input from strings
// use components to preserve separator, split removes separator
let lines = input.components(separatedBy: CharacterSet.newlines)
var tableOfContents = ""
var document = ""
let newLine = "\n" // TODO: Need to support windows newline \r\n ???
// TODO: TOC needs to be generated before we can output it ... needs two passes on the document
for line in lines {
if line.starts(with: "#") {
// TODO: what indentation level?
// TODO: customize the indentation on output (starting level)
// TODO: store all the lines of text
// TODO: do this using unit tests (actual example)
var heading = ""
if let firstSpaceIndex = line.index(of: " ") {
let firstCharacterIndex = line.index(after: firstSpaceIndex) // How do I increment the index position?
heading = line.substring(from: firstCharacterIndex)
}
// line.index(after: line.index(of: " "))
// print(heading)
let indexLevel = "* "
let leftBracket = "["
let rightBracket = "]"
let leftParen = "("
let rightParen = ")"
// remove spaces + special characters
// let noSpecialCharacters = heading.replacingOccurrences(of: "- ", with: "").replacingOccurrences(of: "?", with: "").replacingOccurrences(of: "#", with: "")
// TODO: remove punctuaction
let noSpecialCharacters = heading.components(separatedBy: CharacterSet.punctuationCharacters).joined()
// TODO: test it ... unit tests ...
// TODO: remove double spaces, and special characters
// TODO: lowercase
// TODO: remove extra spaces around a special character "20 - 30 minutes"
var hashLink = noSpecialCharacters.replacingOccurrences(of: " ", with: "-").lowercased()
hashLink = hashLink.components(separatedBy: CharacterSet.symbols).joined()
let listItem = indexLevel + leftBracket + heading + rightBracket +
leftParen + "#" + hashLink + rightParen + newLine
//TODO: warn me if it's not unique (subhead, make it unique by appending unique number or something else
// TODO: This would be a great chunk of code to unit test and build incrementally
tableOfContents += listItem
// print the link above the heading, so that when jumping, the heading
// is visible on the page
// print("<a id=\"\(hashLink)\"></a>")
// print(line) // print the heading
document += "<a id=\"\(hashLink)\"></a>" + newLine
document += (line + newLine)
// TODO: store the headings with indentation levels
// TODO: replace {{TOC}} with actual table of contents
// TODO: Insert tags above headings for links in article
// TODO: String with spaces/special characters to hyphenated string
}
else if line.contains("{{TOC}}") {
// print("TOC!!!")
// print(tableOfContents)
document += line + newLine /// FIXED BUG: was removing, and never added back
} else {
// print(line)
document += line + newLine
}
}
// 2nd pass insert the TOC inplace of {{TOC}}
// for line in document {
// if line.contains("{{TOC}}") {
//
// }
//
// }
// document = document.replacingOccurrences(of: "{{TOC}}", with: "MOOO" ) // why doesn't this work??
// TODO: remove special unicode characters outside normal character set ... I don't know what link characters are allowed ... if there are restrictions on links with <a> tags.
// TODO: How to replace existing TOC
// TODO: Feature to remove and update previous <a id=> tags
// TODO: Feature to open Marked with the updated Markdown document ...
// TODO: Feature to adjust image paths to local paths vs. server
// TODO: is there a server API I can use?
let documentWithTOC = document.replacingOccurrences(of: "{{TOC}}\n", with: tableOfContents, options: [.literal, .caseInsensitive])
// print("INPUT: \n\(document)")
// FIXED issues with newlines and document, since I was trimming before searching again ... nothing to find from 1st pass bug
print("NEW:")
// Alternate logic
// var documentWithTOC = ""
//
//// for line in document.split(separator: "\n") {
// for line in document.components(separatedBy: CharacterSet.newlines) {
// print("N: " + line)
// // FIXME: Splitting on the newline will through out all the white space (newlines) the author had added ...
// if line.contains("{{TOC}}") {
// documentWithTOC += tableOfContents + "\n"
// } else {
// documentWithTOC += (line + "\n")
// }
// }
print(documentWithTOC)
// For some reason it's not working for the big example, but it works for the simple example below
// TODO: why doesn't this work properly?
// print(document)
// print(tableOfContents)
return tableOfContents
}
let toc = createTableOfContentsFromMarkdown(input: input)
// Paul: Test the API, and it works ... but when I use it on the actual data, it doesn't output the correct value
// What's going on????
//let s = "string {{TOC}}\n blah\nblah"
//let t = s.replacingOccurrences(of: "{{TOC}}\n", with: "MOOO", options: [.literal, .caseInsensitive]) // why doesn't this work??
//let u = s.replacingOccurrences(of: "{{TOC}}\n", with: toc, options: [.literal, .caseInsensitive]) // why doesn't this work??
//
//
//print("Original: \"\(s)\"")
//print("Modified: \"\(t)\"")
//print("TOC: \"\(u)\"")
// Remove unicode test
//let str = "hello()[]★★★★☆"
//let str2 = str.components(separatedBy: CharacterSet.symbols).joined()
//for a in str {
// print(a) //.unicodeScalars.first)
//// print(a.)
//}
//
//
//for a in str2 {
// print(a)
// // print(a.)
//}
@PaulSolt
Copy link
Author

This code isn't production ready and has some bugs, but I wanted to share what "prototype" code looks like.

It doesn't even do the thing it's suppose to do yet, but I'll fix that in a new version when I clean it up after the prototype stage. Ideally I want to write unit tests to verify it works with the expected inputs/outputs, so it doesn't trash my blog posts ... that would be bad!

@Jignesh1805
Copy link

you can use wrong pattern

@PaulSolt
Copy link
Author

Found the bug, I didn't add back the {{TOC}} line when it was found so that I couldn't do the second pass.

Lots of clean up to do, and I need to write unit tests ... that's one thing I don't like about Playgrounds (along with no debugging)

@PaulSolt
Copy link
Author

Thanks @Jignesh1805 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment