lib/onebox/engine/github_blob_onebox.rb in onebox-1.5.0 vs lib/onebox/engine/github_blob_onebox.rb in onebox-1.5.1
- old
+ new
@@ -2,52 +2,206 @@
module Engine
class GithubBlobOnebox
include Engine
include LayoutSupport
- MAX_LINES = 20
- MAX_CHARS = 5000
-
+ EXPAND_AFTER = 0b001
+ EXPAND_BEFORE = 0b010
+ EXPAND_NONE = 0b0
+
+ DEFAULTS = {
+ :EXPAND_ONE_LINER => EXPAND_AFTER|EXPAND_BEFORE, #set how to expand a one liner. user EXPAND_NONE to disable expand
+ :LINES_BEFORE => 10,
+ :LINES_AFTER => 10,
+ :SHOW_LINE_NUMBER => true,
+ :MAX_LINES => 20,
+ :MAX_CHARS => 5000
+ }
+
matches_regexp(/^https?:\/\/(www\.)?github\.com.*\/blob\//)
+
+
+ def initialize(link, cache = nil, timeout = nil)
+ super link, cache , timeout
+ #merge engine options from global Onebox.options interface
+ # self.options = Onebox.options["GithubBlobOnebox"] # self.class.name.split("::").last.to_s
+ # self.options = Onebox.options[self.class.name.split("::").last.to_s] #We can use this a more generic approach. extract the engine class name automatically
+
+ self.options = DEFAULTS
+
+ # Define constant after merging options set in Onebox.options
+ # We can define constant automatically.
+ options.each_pair {|constant_name,value|
+ constant_name_u = constant_name.to_s.upcase
+ if constant_name_u == constant_name.to_s
+ #define a constant if not already defined
+ self.class.const_set constant_name_u.to_sym , options[constant_name_u.to_sym] unless self.class.const_defined? constant_name_u.to_sym
+ end
+ }
+ end
+
private
+ @selected_lines_array = nil
+ @selected_one_liner = 0
+ def calc_range(m,contents_lines_size)
+ #author Lidlanca 09/15/2014
+ truncated = false
+ from = /\d+/.match(m[:from]) #get numeric should only match a positive interger
+ to = /\d+/.match(m[:to]) #get numeric should only match a positive interger
+ range_provided = !(from.nil? && to.nil?) #true if "from" or "to" provided in URL
+ from = from.nil? ? 1 : from[0].to_i #if from not provided default to 1st line
+ to = to.nil? ? -1 : to[0].to_i #if to not provided default to undefiend to be handled later in the logic
+
+ if to === -1 && range_provided #case "from" exists but no valid "to". aka ONE_LINER
+ one_liner = true
+ to = from
+ else
+ one_liner = false
+ end
+ unless range_provided #case no range provided default to 1..MAX_LINES
+ from = 1
+ to = MAX_LINES
+ truncated = true if contents_lines_size > MAX_LINES
+ #we can technically return here
+ end
+
+ from, to = [from,to].sort #enforce valid range. [from < to]
+ from = 1 if from > contents_lines_size #if "from" out of TOP bound set to 1st line
+ to = contents_lines_size if to > contents_lines_size #if "to" is out of TOP bound set to last line.
+
+ if one_liner
+ @selected_one_liner = from
+ if EXPAND_ONE_LINER != EXPAND_NONE
+ if (EXPAND_ONE_LINER & EXPAND_BEFORE != 0) # check if EXPAND_BEFORE flag is on
+ from = [1, from - LINES_BEFORE].max # make sure expand before does not go out of bound
+ end
+
+ if (EXPAND_ONE_LINER & EXPAND_AFTER != 0) # check if EXPAND_FLAG flag is on
+ to = [to + LINES_AFTER, contents_lines_size].min # make sure expand after does not go out of bound
+ end
+
+ from = contents_lines_size if from > contents_lines_size #if "from" is out of the content top bound
+ # to = contents_lines_size if to > contents_lines_size #if "to" is out of the content top bound
+ else
+ #no expand show the one liner solely
+ end
+ end
+
+ if to-from > MAX_LINES && !one_liner #if exceed the MAX_LINES limit correct unless range was produced by one_liner which it expand setting will allow exceeding the line limit
+ truncated = true
+ to = from + MAX_LINES-1
+ end
+
+ {:from => from, #calculated from
+ :from_minus_one => from-1, #used for getting currect ol>li numbering with css used in template
+ :to => to, #calculated to
+ :one_liner => one_liner, #boolean if a one-liner
+ :selected_one_liner => @selected_one_liner, #if a one liner is provided we create a reference for it.
+ :range_provided => range_provided, #boolean if range provided
+ :truncated => truncated}
+ end
+
+ #minimize/compact leading indentation while preserving overall indentation
+ def removeLeadingIndentation str
+ #author Lidlanca 2014
+ min_space=100
+ a_lines = str.lines
+ a_lines.each {|l|
+ l = l.chomp("\n") # remove new line
+ m = l.match /^[ ]*/ # find leading spaces 0 or more
+ unless m.nil? || l.size==m[0].size || m[0].size==0 # no match | only spaces in line | empty line
+ m_str_length = m[0].size
+ if m_str_length <= 1 # minimum space is 1 or nothing we can break we found our minimum
+ min_space = m_str_length
+ break #stop iteration
+ end
+ if m_str_length < min_space
+ min_space = m_str_length
+ end
+ else
+ next # SKIP no match or line is only spaces
+ end
+ }
+ a_lines.each {|l|
+ re = Regexp.new "^[ ]{#{min_space}}" #match the minimum spaces of the line
+ l.gsub!(re, "")
+ }
+ a_lines.join
+ end
+
+ def line_number_helper(lines,start,selected)
+ #author Lidlanca 09/15/2014
+ lines = removeLeadingIndentation(lines.join).lines # A little ineffeicent we could modify removeLeadingIndentation to accept array and return array, but for now it is only working with a string
+ hash_builder =[]
+ output_builder = []
+ lines.map.with_index { |line,i|
+ lnum = (i.to_i+start)
+ hash_builder.push({:line_number => lnum, :data=> line.gsub("\n",""), :selected=> (selected==lnum)? true: false} )
+ output_builder.push "#{lnum}: #{line}"
+ }
+ {:output=>output_builder.join(), :array=>hash_builder}
+ end
+
+
def raw
+ options_id = self.class.name.split("::").last.to_s #get class name without module namespace
+
return @raw if @raw
m = @url.match(/github\.com\/(?<user>[^\/]+)\/(?<repo>[^\/]+)\/blob\/(?<sha1>[^\/]+)\/(?<file>[^#]+)(#(L(?<from>[^-]*)(-L(?<to>.*))?))?/mi)
+
if m
- from = (m[:from] || -1).to_i
- to = (m[:to] || -1).to_i
+ from = /\d+/.match(m[:from]) #get numeric should only match a positive interger
+ to = /\d+/.match(m[:to]) #get numeric should only match a positive interger
+
@file = m[:file]
contents = open("https://raw.github.com/#{m[:user]}/#{m[:repo]}/#{m[:sha1]}/#{m[:file]}", read_timeout: timeout).read
- if from > 0
- if to < 0
- from = from - 10
- to = from + 20
+
+ contents_lines = contents.lines #get contents lines
+ contents_lines_size = contents_lines.size #get number of lines
+
+ cr = calc_range(m,contents_lines_size) #calculate the range of lines for output
+ selected_one_liner = cr[:selected_one_liner] #if url is a one-liner calc_range will return it
+ # puts "SELECTED LINE" + cr[:selected_one_liner].to_s
+ from = cr[:from]
+ to = cr[:to]
+ @truncated = cr[:truncated]
+ range_provided = cr[:range_provided]
+ one_liner = cr[:one_liner]
+ @cr_results = cr
+ if range_provided #if a range provided (single line or more)
+ if SHOW_LINE_NUMBER
+ lines_result = line_number_helper(contents_lines[from-1..to-1], from, selected_one_liner) #print code with prefix line numbers in case range provided
+ contents = lines_result[:output]
+ @selected_lines_array = lines_result[:array]
+ else
+ contents = contents_lines[from-1..to-1].join()
end
- if to > from
- contents = contents.split("\n")[from..to].join("\n")
- end
+
+ else
+ contents = contents_lines[from-1..to-1].join()
end
- if contents.length > MAX_CHARS
+
+ if contents.length > MAX_CHARS #truncate content chars to limits
contents = contents[0..MAX_CHARS]
@truncated = true
end
- split = contents.split("\n")
- if split.length > MAX_LINES
- contents = split[0..MAX_LINES].join("\n")
- @truncated = true
- end
@raw = contents
end
end
def data
@data ||= {title: link.sub(/^https?\:\/\/github\.com\//, ''),
link: link,
content: raw,
+ lines: @selected_lines_array ,
+ has_lines: !@selected_lines_array.nil?,
+ selected_one_liner: @selected_one_liner,
+ cr_results:@cr_results,
truncated: @truncated}
end
+
end
end
end