rcmdnk's blog

Mandrill

Once you became to have a lot of posts, Octopress’ rake generate command could take a long time.

When you want to check your site, normally it is needed to build only your new post. While Octopress has tasks of isolate/integrate to achieve such usage, it needs some manual commands, and there is a risk to push your site with only a new post.

In this post, I will introduce how to speed up rake generate with an easy command, and in safety.

Sponsored Links

isolate/integrate update

rake isolate[my-post] command stashes all posts other than my-post from source/_post directory. They are stashed in source/_stash, which are not used for a build, so that Jekyll command builds only my-post.

After checking your site, you can revert them by rake integrate.

But there are some difficulties:

  • You need some commands to build one time for a check.
    • In addition, you need to give a name of a post.
  • You may deploy your site only with a new post.
  • isolate command doesn’t move a directory in _post directory.
    • I put my old posts in such source/_post/y_2014, because Octopress (Jekyll) manages these posts under subdirectories as same as posts under source/_post.
  • Pages are not stashed.

To solve them, I modified Rakefile as below for isolate/integrate tasks:

Rakefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
 ## -- Misc Configs -- ##
stash_dir       = "_stash"    # directory to stash posts for speedy generation
full_stash_dir  = "#{source_dir}/#{stash_dir}"    # full path for stash dir
stash_root_dir  = "_stash_root" # directory to stash pages (in /source/)
full_stash_root_dir = "#{source_dir}/#{stash_root_dir}" # full path for stash_root dir
root_stashes    = ['Your-Page'] # directories to be stashed in /source/


# usage rake isolate[my-post]
desc "Move all other posts than the one currently being worked on to a temporary stash location (stash) so regenerating the site happens much more quickly."
task :isolate, :filename do |t, args|
  if args.filename
    filename = args.filename
  else
    filename = Dir.glob("#{source_dir}/#{posts_dir}/*.#{new_post_ext}").sort_by{|f| File.mtime(f)}.last
  end
  if filename == nil
    puts ""
    puts "There is no markdown file (*.#{new_post_ext}) in #{source_dir}/#{posts_dir}."
    exit 1
  end
  puts "## Stashing other posts than #{filename}"
  FileUtils.mkdir(full_stash_dir) unless File.exist?(full_stash_dir)
  Dir.glob("#{source_dir}/#{posts_dir}/*") do |post|
    if post.include?(filename)
      p "Remaining #{post}..."
    else
      FileUtils.mv post, full_stash_dir
    end
  end
  FileUtils.mkdir(full_stash_root_dir) unless File.exist?(full_stash_root_dir)
  if defined? root_stashes == nil
    if root_stashes.class == String
      FileUtils.mv "#{source_dir}/#{root_stashes}", full_stash_root_dir
    elsif root_stashes.class == Array
      for d in root_stashes do
        FileUtils.mv "#{source_dir}/#{d}", full_stash_root_dir
      end
    end
  end
  system "touch .isolated"
end

desc "Move all stashed posts back into the posts directory, ready for site generation."
task :integrate do
  FileUtils.mv Dir.glob("#{full_stash_dir}/*"), "#{source_dir}/#{posts_dir}/"
  FileUtils.mv Dir.glob("#{full_stash_root_dir}/*"), "#{source_dir}/"
  system "rm -f .isolated"
  system "touch .integrated"
end

First, new variables full_stash_dir, stash_root_dir, full_stash_root_dir and root_stashes are added.

full_stash_dir is introduced because in Octopress’ Rakefile overwrites stash_dir values in isolate task, so that it will fail if we use isolate and integrate in a sequence.

With above modifications of isolate/integrate, it is actually not needed, but use them just for making paths clear.

stash_root_dir is used for pages instead of posts. (Pages are normally in a root directory (source/).)

full_stash_root_dir is full path name for stash_root_dir.

And root_stashes are what you want to stash in isolate task. Above setting will move source/Your-Page in source/_stash_root in isolate task. You can give multiple pages as an array.

Next, isolate task is now able to select the last modified post automatically if you don’t give a post name. The posts are sorted by mtime (modified time) and the last one is used.

mv command loop was modified to move all files/directories in source/_post directory. In addition, it moves pages listed in root_stashes in source/.

In the last of isolate task, it makes .isolated file in Octopress’ top directory. It is used in deploy command to check if files are isolated or not. (see below).

integrated task now moves files/directories in full_stash_root_dir, too. In addition, it deletes .isolated, and makes .integrated. It is used in deploy command, too.

generate_only command

Then, make a task which build only one post automatically.

Rakefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# usage rake generate_only[my-post]
desc "Generate only specified post (much faster)"
task :generate_only, :filename do |t, args|
  raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir)
  trap(:INT) do
    raise "Stopped by SIGINT"
  end
  begin
    Rake::Task[:isolate].invoke(args.filename)
    puts "## Generating Site with Jekyll"
    ok_failed_raise(system("compass compile --css-dir #{source_dir}/stylesheets"), false)
    ok_failed_raise(system("jekyll build --unpublished"), false)
    puts "## Restoring stashed posts/pages"
    Rake::Task[:integrate].execute
  rescue
    Rake::Task[:integrate].execute
    raise $!
  end
end

desc "Same as generate_only"
task :gen_only, :filename do |t, args|
  Rake::Task[:generate_only].invoke(args.filename)
end

desc "Same as generate_only"
task :go, :filename do |t, args|
  Rake::Task[:generate_only].invoke(args.filename)
end

Here, rake generate_only task executes isolate, generate (compass/jekyll) and integrate.

gen_only and go are aliases for generate_only.

Now you can generate only your new post with

$ rake generate_only["my-latest-post"]

or

$ rake generate_only

for the last modified post. You can even use:

$ rake go

as same as rake generate_only.

Similar settings can be used for watch and preview:

Rakefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# usage rake watch_only[my-post]
desc "watch only specified post"
task :watch_only, :filename do |t, args|
  raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir)
  Rake::Task[:isolate].invoke(args.filename)
  begin
    Rake::Task[:watch].execute
  rescue
  end
  puts ""
  puts "## Restoring stashed posts/pages"
  Rake::Task[:integrate].execute
end

# usage rake preview_only[my-post]
desc "preview only specified post"
task :preview_only, :filename do |t, args|
  raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir)
  Rake::Task[:isolate].invoke(args.filename)
  begin
    Rake::Task[:preview].execute
  rescue
  end
  puts ""
  puts "## Restoring stashed posts/pages"
  Rake::Task[:integrate].execute
end

Assure full build

In deploy task, it should assure that you built with all files w/o stashed files.

In Octopress’ Rakefile, there is a similar method using .preview-mode file, which is made in preview or watch tasks 1

In below, deploy task checks .integrated or .isolated files, and re-generates after integrate if there is one of these files.

In addition, generate task deletes .integrated (and .preview-mode) as full files build are done at that point.

Rakefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
desc "Generate jekyll site"
task :generate do
  raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir)
  Rake::Task[:check].invoke()
  puts "## Generating Site with Jekyll"
  system "compass compile --css-dir #{source_dir}/stylesheets"
  system "jekyll build"
  system "rm -f .integrated"
  system "rm -f .preview-mode"
end

desc "Default deploy task"
task :deploy do
  # Check if preview posts exist, which should not be published
  if File.exists?(".integrated") or File.exists?(".isolated")
    puts "## Found isolated history, regenerating files ..."
    system "rm -f .integrated .isolated"
    Rake::Task[:integrate].execute
    Rake::Task[:generate].execute
  end
  if File.exists?(".preview-mode")
    puts "## Found posts in preview mode, regenerating files ..."
    File.delete(".preview-mode")
    Rake::Task[:generate].execute
  end

  ...

end

Then, you can safely use rake generate_only command.

These modifications can be seen in Octogray theme:

Sponsored Links
  1. Note: currently .preview-mode is remained only in deploy task. It was somehow removed from preview or watch tasks. So it checks in deploy, but the file is not created in preview or watch, it is meaningless.

Sponsored Links

« Brew-file: Manager for packages of Homebrew Send a mail by JavaScript with Mandrill »

}