fngtps-weblog 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/TODO ADDED
@@ -0,0 +1,5 @@
1
+ NOTES
2
+
3
+ git config --add user.name 'Manfred Stienstra'
4
+ git config --add user.email 'manfred@fngtps.com'
5
+ gem install rdiscount erubis git json
data/bin/weblog ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if $0 == __FILE__
4
+ require 'rubygems'
5
+ $:.unshift(File.expand_path('../../lib', __FILE__))
6
+ end
7
+
8
+ require 'weblog'
9
+ require 'option_parser'
10
+
11
+ def usage
12
+ puts "Usage: #{File.basename($0)} <command>"
13
+ puts ""
14
+ puts "Commands:"
15
+ puts " init: Initialize a directory for use with the weblog tool"
16
+ puts " generate: Convert all Markdown source to an HTML file"
17
+ puts " publish: Move a draft directory into the public directory"
18
+ puts " info: Print information about the current working directory"
19
+ puts "Options:"
20
+ puts " -v, --verbose: Print informative messages"
21
+ end
22
+
23
+ options = {}
24
+ flags, args = OptionParser.parse(ARGV)
25
+
26
+ flags.each do |key, value|
27
+ case key
28
+ when 'h', 'help'
29
+ usage
30
+ exit 0
31
+ when 'v', 'verbose'
32
+ options[:verbose] = true
33
+ end
34
+ end
35
+
36
+ case args[0]
37
+ when 'init'
38
+ Weblog.init(options)
39
+ when 'publish'
40
+ Weblog.publish(options, *args[1..-1])
41
+ when 'info'
42
+ Weblog.print_info(options)
43
+ when 'help'
44
+ usage
45
+ else
46
+ Weblog.generate(options)
47
+ end
@@ -0,0 +1,28 @@
1
+ class OptionParser
2
+ def self.parse(argv)
3
+ return [{},[]] if argv.empty?
4
+
5
+ options = {}
6
+ rest = []
7
+ switch = nil
8
+
9
+ for value in argv
10
+ # values is a switch
11
+ if value[0] == 45
12
+ switch = value.slice((value[1] == 45 ? 2 : 1)..-1)
13
+ options[switch] = nil
14
+ else
15
+ if switch
16
+ # we encountered a switch so this
17
+ # value belongs to that switch
18
+ options[switch] = value
19
+ switch = nil
20
+ else
21
+ rest << value
22
+ end
23
+ end
24
+ end
25
+
26
+ [options, rest]
27
+ end
28
+ end
data/lib/weblog.rb ADDED
@@ -0,0 +1,155 @@
1
+ require 'set'
2
+ require 'time'
3
+ require 'fileutils'
4
+
5
+ require 'rdiscount'
6
+ require 'erubis'
7
+ require 'json'
8
+ require 'git'
9
+
10
+ class Weblog
11
+ autoload :Helpers, "weblog/helpers"
12
+ autoload :Index, "weblog/index"
13
+ autoload :Month, "weblog/month"
14
+ autoload :Snippet, "weblog/snippet"
15
+ autoload :Post, "weblog/post"
16
+
17
+ DIRECTORIES = %w(
18
+ public/javascripts
19
+ public/stylesheets
20
+ draft
21
+ )
22
+
23
+ # --- Utilities
24
+
25
+ def self.source_root
26
+ File.expand_path('../../', __FILE__)
27
+ end
28
+
29
+ def self.template_path
30
+ File.join(source_root, 'templates')
31
+ end
32
+
33
+ def self.find_working_directory(cwd)
34
+ candidate = cwd
35
+ while(candidate)
36
+ directory = File.join(candidate, 'public')
37
+ if File.exist?(directory)
38
+ return candidate
39
+ else
40
+ parts = candidate.split('/')
41
+ candidate = parts.empty? ? false : parts[0..-2].join('/')
42
+ end
43
+ end; nil
44
+ end
45
+
46
+ def initialize(cwd, options={})
47
+ @options = options
48
+ unless @path = self.class.find_working_directory(cwd)
49
+ puts "[!] Please use this tool from somewhere within the working directory"
50
+ end
51
+ end
52
+
53
+ def git
54
+ @git ||= Git.open(@path)
55
+ end
56
+
57
+ def verbose?
58
+ @options[:verbose]
59
+ end
60
+
61
+ # --- Attributes
62
+
63
+ attr_accessor :path
64
+
65
+ def title
66
+ @path.split('/').last.gsub(/[_-]/, ' ').capitalize
67
+ end
68
+
69
+ def months
70
+ Month.all(self)
71
+ end
72
+
73
+ def posts
74
+ if @posts.nil?
75
+ paths = Set.new
76
+ base_path = File.join(@path, 'public')
77
+ path_length = base_path.split('/').length
78
+ Dir.glob(File.join(base_path, '**/post.*')).each do |file|
79
+ paths << file.split('/')[path_length..-2].join('/')
80
+ end
81
+ @posts = paths.map do |path|
82
+ Weblog::Post.new(self, path)
83
+ end
84
+ end; @posts
85
+ end
86
+
87
+ def size
88
+ posts.size
89
+ end
90
+
91
+ def author
92
+ git.config('user.name')
93
+ end
94
+
95
+ # --- Commands
96
+
97
+ def index
98
+ Weblog::Index.new(self)
99
+ end
100
+
101
+ def generate
102
+ posts.each { |post| post.generate }
103
+ months.each { |month| month.generate }
104
+ index.generate
105
+ end
106
+
107
+ def publish(*directories)
108
+ year = Time.now.year.to_s
109
+ month = "%02d" % Time.now.month
110
+ directories.each do |directory|
111
+ name = directory.split('/').last
112
+ destination = File.join(@path, 'public', year, month, name)
113
+ FileUtils.mkdir_p(File.dirname(destination))
114
+ FileUtils.mv(directory, destination)
115
+ end
116
+ end
117
+
118
+ def print_info
119
+ months.each do |month|
120
+ puts "#{month.path}: #{month.size} #{month.size == 1 ? 'post' : 'posts'}"
121
+ end
122
+ puts [
123
+ "Directory: #{@path}",
124
+ "Title: #{title}",
125
+ "Author: #{author}",
126
+ "Total posts: #{size} #{size == 1 ? 'post' : 'posts'}"
127
+ ].join("\n")
128
+ end
129
+
130
+ def self.init(options)
131
+ DIRECTORIES.each { |path| FileUtils.mkdir_p(path) }
132
+ unless File.exist?('templates')
133
+ FileUtils.cp_r(template_path, 'templates')
134
+ FileUtils.rm_rf('templates/.svn')
135
+ end
136
+ end
137
+
138
+ def self.generate(options)
139
+ weblog = new(Dir.pwd, options)
140
+ weblog.generate
141
+ weblog
142
+ end
143
+
144
+ def self.publish(options, *directories)
145
+ weblog = new(Dir.pwd, options)
146
+ weblog.publish(*directories)
147
+ weblog
148
+ end
149
+
150
+ def self.print_info(options)
151
+ weblog = new(Dir.pwd, options)
152
+ weblog.print_info
153
+ weblog
154
+ end
155
+ end
@@ -0,0 +1,13 @@
1
+ class Weblog
2
+ module Helpers
3
+ def format_date(date)
4
+ return '' if date.nil?
5
+ date.strftime("%d-%m-%Y")
6
+ end
7
+
8
+ def format_date_and_time(time)
9
+ return '' if time.nil?
10
+ time.strftime("#{format_date(time)}, %H:%M")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,56 @@
1
+ class Weblog
2
+ class Index
3
+ SIZE = 5
4
+
5
+ def initialize(weblog)
6
+ @weblog = weblog
7
+ end
8
+
9
+ def posts
10
+ if @posts.nil?
11
+ @posts = []
12
+ @weblog.months.reverse.each do |month|
13
+ month.posts.each do |post|
14
+ @posts << post
15
+ return @posts if @posts.length >= SIZE
16
+ end
17
+ end
18
+ end; @posts
19
+ end
20
+
21
+ # --- Rendering system
22
+
23
+ include Weblog::Helpers
24
+
25
+ def html_template_filename
26
+ File.join(@weblog.path, "templates/index.html.erb")
27
+ end
28
+
29
+ def feed_template_filename
30
+ File.join(@weblog.path, "templates/index.rss.erb")
31
+ end
32
+
33
+ def render(template_filename)
34
+ Erubis::EscapedEruby.new(
35
+ File.read(template_filename)
36
+ ).result(binding)
37
+ end
38
+
39
+ def html_filename
40
+ File.join(@weblog.path, 'public', 'weblog.html')
41
+ end
42
+
43
+ def feed_filename
44
+ File.join(@weblog.path, 'public', 'weblog.rss')
45
+ end
46
+
47
+ def generate
48
+ FileUtils.mkdir_p(File.dirname(html_filename))
49
+ File.open(html_filename, 'w') { |file| file.write(render(html_template_filename)) }
50
+ puts "Generated weblog index" if @weblog.verbose?
51
+ FileUtils.mkdir_p(File.dirname(feed_filename))
52
+ File.open(feed_filename, 'w') { |file| file.write(render(feed_template_filename)) }
53
+ puts "Generated weblog feed" if @weblog.verbose?
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,67 @@
1
+ class Weblog
2
+ class Month
3
+ attr_accessor :year, :month
4
+
5
+ def initialize(weblog, year, month)
6
+ @weblog = weblog
7
+ @year = year.to_i
8
+ @month = month.to_i
9
+ @path = File.join(@year.to_s, ("%02d" % @month))
10
+ end
11
+
12
+ attr_reader :path
13
+
14
+ # Returns a list of posts for this month in reverse chronological order
15
+ def posts
16
+ @weblog.posts.select do |post|
17
+ post.path.start_with?(@path)
18
+ end.sort_by { |post| -post.published_at.to_i }
19
+ end
20
+
21
+ def size
22
+ posts.size
23
+ end
24
+
25
+ def self.all(weblog)
26
+ months = []
27
+ Dir.entries(File.join(weblog.path, 'public')).each do |year|
28
+ if year =~ /^\d{4}$/
29
+ Dir.entries(File.join(weblog.path, 'public', year)).each do |month|
30
+ if month =~ /^\d{2}$/
31
+ months << new(weblog, year, month)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ months.sort_by do |month|
37
+ [month.year, month.month]
38
+ end
39
+ end
40
+
41
+ # --- Rendering system
42
+
43
+ include Weblog::Helpers
44
+
45
+ def template_filename
46
+ File.join(@weblog.path, "templates/month.html.erb")
47
+ end
48
+
49
+ def render
50
+ Erubis::EscapedEruby.new(
51
+ File.read(template_filename)
52
+ ).result(binding)
53
+ end
54
+
55
+ def index_filename
56
+ File.join(@weblog.path, 'public', @path, 'index.html')
57
+ end
58
+
59
+ def generate
60
+ FileUtils.mkdir_p(File.dirname(index_filename))
61
+ File.open(index_filename, 'w') do |file|
62
+ file.write(render)
63
+ end
64
+ puts "Generated #{@year}/#{"%02d" % @month}" if @weblog.verbose?
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,154 @@
1
+ class Weblog
2
+ class Post
3
+ attr_accessor :path
4
+
5
+ def initialize(weblog, path)
6
+ @weblog = weblog
7
+ @path = path
8
+ @post = self
9
+ _load_metadata
10
+ end
11
+
12
+ # --- Metadata
13
+
14
+ def metadata_filename
15
+ File.join(@weblog.path, 'public', @path, 'post.json')
16
+ end
17
+
18
+ attr_writer :author
19
+
20
+ def published_at=(published_at)
21
+ @published_at = Time.parse(published_at)
22
+ end
23
+
24
+ def updated_at=(updated_at)
25
+ @updated_at = Time.parse(updated_at)
26
+ end
27
+
28
+ def _load_metadata
29
+ JSON.parse(File.read(metadata_filename)).each do |key, value|
30
+ self.send("#{key}=", value)
31
+ end if File.exist?(metadata_filename)
32
+ end
33
+
34
+ # --- Attributes
35
+
36
+ def blob
37
+ if File.exist?(index_filename)
38
+ @blob ||= @weblog.git.gblob(index_filename)
39
+ end
40
+ end
41
+
42
+ def last_commit
43
+ @last_commit ||= blob.log(1).first if blob
44
+ end
45
+
46
+ def first_commit
47
+ @first_commit ||= blob.log(10_000).map{|l|l}.last if blob
48
+ end
49
+
50
+ def modified?
51
+ @modified ||= @weblog.git.status.changed.any? do |filename, status|
52
+ snippet.filename.end_with?(filename)
53
+ end
54
+ end
55
+
56
+ # * Author:
57
+ # - When it's in the git repository: first commit author
58
+ # - Otherwise: configured author
59
+ def _author
60
+ if commit = first_commit
61
+ first_commit.author.name
62
+ else
63
+ @weblog.author
64
+ end
65
+ end
66
+
67
+ def author
68
+ @author ||= _author
69
+ end
70
+
71
+ # * The created date:
72
+ # - When it's not in the git repository: current date and time
73
+ # - When it's in the git repository: first commit date
74
+ def _published_at
75
+ if commit = first_commit
76
+ commit.date
77
+ else
78
+ Time.now
79
+ end
80
+ end
81
+
82
+ def published_at
83
+ @published_at ||= _published_at
84
+ end
85
+
86
+ # * The updated date:
87
+ # - When it's not in the git repository: nil
88
+ # - When it has modifications: current date and time
89
+ # - When it has no modifications: last commit date
90
+ def _updated_at
91
+ if modified?
92
+ Time.now
93
+ elsif commit = last_commit
94
+ commit.date
95
+ else
96
+ nil
97
+ end
98
+ end
99
+
100
+ def updated_at
101
+ @updated_at ||= _updated_at
102
+ end
103
+
104
+ def stylesheets
105
+ Dir.glob(File.join(@weblog.path, 'public', path, '*.css')).map do |path|
106
+ File.basename(path)
107
+ end
108
+ end
109
+
110
+ def javascripts
111
+ Dir.glob(File.join(@weblog.path, 'public', path, '*.js')).map do |path|
112
+ File.basename(path)
113
+ end
114
+ end
115
+
116
+ def snippet
117
+ @snippet ||= Snippet.new(@weblog, self, File.join('public', path))
118
+ end
119
+
120
+ def title
121
+ snippet.title.strip
122
+ end
123
+
124
+ def content
125
+ snippet.content
126
+ end
127
+
128
+ # --- Rendering system
129
+
130
+ include Weblog::Helpers
131
+
132
+ def template_filename
133
+ File.join(@weblog.path, "templates/post.html.erb")
134
+ end
135
+
136
+ def render
137
+ Erubis::EscapedEruby.new(
138
+ File.read(template_filename)
139
+ ).result(binding)
140
+ end
141
+
142
+ def index_filename
143
+ File.join(@weblog.path, 'public', @path, 'index.html')
144
+ end
145
+
146
+ def generate
147
+ FileUtils.mkdir_p(File.dirname(index_filename))
148
+ File.open(index_filename, 'w') do |file|
149
+ file.write(render)
150
+ end
151
+ puts "Generated #{path}" if @weblog.verbose?
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,51 @@
1
+ class Weblog
2
+ class Snippet
3
+ attr_accessor :path, :filename
4
+
5
+ def initialize(weblog, object, path)
6
+ @weblog = weblog
7
+ @path = path
8
+ find_snippet_file
9
+ end
10
+
11
+ def candidates
12
+ %w(post.md post.html).map do |name|
13
+ File.join(@weblog.path, @path, name)
14
+ end
15
+ end
16
+
17
+ def find_snippet_file
18
+ @filename = candidates.detect do |filename|
19
+ File.exist?(filename)
20
+ end
21
+ end
22
+
23
+ def source_type
24
+ @filename.end_with?('.md') ? :markdown : :html
25
+ end
26
+
27
+ def read
28
+ @read ||= File.read(filename).split("\n")
29
+ end
30
+
31
+ def title
32
+ return '' if read.empty?
33
+ case source_type
34
+ when :markdown
35
+ /^#(.*)$/.match(read[0])[1]
36
+ when :html
37
+ /<h1>(.*)<\/h1>/.match(read[0])[1]
38
+ end
39
+ end
40
+
41
+ def content
42
+ return '' if read.empty?
43
+ case source_type
44
+ when :markdown
45
+ RDiscount.new(read[1..-1].join("\n")).to_html
46
+ when :html
47
+ read[1..-1].join("\n")
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= @weblog.title %></title>
5
+ <meta charset="utf-8" />
6
+ </head>
7
+ <body>
8
+ <% posts.each do |post| %>
9
+ <h1><a href="<%= post.path %>"><%= post.title %></a></h1>
10
+ <%== post.content %>
11
+ <% end %>
12
+ </body>
13
+ </html>
@@ -0,0 +1,23 @@
1
+ <?xml version="1.0"?>
2
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
3
+ <channel>
4
+ <title><%= @weblog.title %></title>
5
+ <atom:id>http://www.fngtps.com/index.rss</atom:id>
6
+ <atom:link rel="self" type="application/rss+xml" href="http://www.fngtps.com/index.rss"/>
7
+ <link>http://www.fngtps.com/index.rss</link>
8
+ <description>Recent posts on ‘Fingertips’</description>
9
+ <% unless posts.empty? %>
10
+ <lastBuildDate><%= (posts.first.updated_at || posts.first.published_at).rfc822 %></lastBuildDate>
11
+ <% end %>
12
+ <% posts.each do |post| %><% if post.published_at %>
13
+ <item>
14
+ <title><%= post.title %></title>
15
+ <link>http://www.fngtps.com/<%= post.path %></link>
16
+ <description><%= post.content %></description>
17
+ <pubDate><%= post.published_at.rfc822 %></pubDate>
18
+ <atom:updated><%= (post.updated_at || post.published_at).rfc822 %></atom:updated>
19
+ <guid>http://www.fngtps.com/<%= post.path %></guid>
20
+ </item>
21
+ <% end %><% end %>
22
+ </channel>
23
+ </rss>
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= @weblog.title %></title>
5
+ <meta charset="utf-8" />
6
+ </head>
7
+ <body>
8
+ <% posts.each do |post| %>
9
+ <h1><a href="<%= post.path.split('/').last %>"><%= post.title %></a></h1>
10
+ <%== post.content %>
11
+ <% end %>
12
+ </body>
13
+ </html>
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= @weblog.title %></title>
5
+ <meta charset="utf-8" />
6
+ <% @post.stylesheets.each do |path| %>
7
+ <link rel="stylesheet" href="<%= path %>" />
8
+ <% end %>
9
+ </head>
10
+ <body>
11
+ <h1><%= @post.title %></h1>
12
+ <%== @post.content %>
13
+ <p><%= @post.author %>, <%= format_date(@post.published_at) %><% if @post.updated_at %>, updated at <%= format_date_and_time(@post.updated_at) %><% end %></p>
14
+ <% @post.javascripts.each do |path| %>
15
+ <script src="<%= path %>" type="text/javascript"></script>
16
+ <% end %>
17
+ </body>
18
+ </html>
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fngtps-weblog
3
+ version: !ruby/object:Gem::Version
4
+ hash: 11
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
+ platform: ruby
12
+ authors:
13
+ - Manfred Stienstra
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-18 00:00:00 +01:00
19
+ default_executable: weblog
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rdiscount
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: erubis
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: git
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :runtime
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: json
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :runtime
76
+ version_requirements: *id004
77
+ - !ruby/object:Gem::Dependency
78
+ name: mocha
79
+ prerelease: false
80
+ requirement: &id005 !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ type: :development
90
+ version_requirements: *id005
91
+ - !ruby/object:Gem::Dependency
92
+ name: test-spec
93
+ prerelease: false
94
+ requirement: &id006 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ type: :development
104
+ version_requirements: *id006
105
+ description: Weblog tools.
106
+ email: manfred@fngtps.com
107
+ executables:
108
+ - weblog
109
+ extensions: []
110
+
111
+ extra_rdoc_files:
112
+ - TODO
113
+ files:
114
+ - bin/weblog
115
+ - lib/option_parser.rb
116
+ - lib/weblog.rb
117
+ - lib/weblog/helpers.rb
118
+ - lib/weblog/index.rb
119
+ - lib/weblog/month.rb
120
+ - lib/weblog/post.rb
121
+ - lib/weblog/snippet.rb
122
+ - templates/index.html.erb
123
+ - templates/index.rss.erb
124
+ - templates/month.html.erb
125
+ - templates/post.html.erb
126
+ - TODO
127
+ has_rdoc: true
128
+ homepage:
129
+ licenses: []
130
+
131
+ post_install_message:
132
+ rdoc_options:
133
+ - --charset=UTF-8
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ hash: 3
142
+ segments:
143
+ - 0
144
+ version: "0"
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ none: false
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ hash: 3
151
+ segments:
152
+ - 0
153
+ version: "0"
154
+ requirements: []
155
+
156
+ rubyforge_project:
157
+ rubygems_version: 1.3.7
158
+ signing_key:
159
+ specification_version: 3
160
+ summary: Weblog tools.
161
+ test_files: []
162
+