qippet 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -1
- data/lib/qippet/boxes/box.rb +74 -0
- data/lib/qippet/boxes/code_box.rb +133 -0
- data/lib/qippet/boxes/container_box.rb +12 -0
- data/lib/qippet/boxes/text_box.rb +12 -0
- data/lib/qippet/builder/xml.rb +65 -0
- data/lib/qippet/builder.rb +22 -0
- data/lib/qippet/colors.rb +19 -0
- data/lib/qippet/extract.rb +38 -0
- data/lib/qippet/version.rb +1 -1
- data/lib/qippet.rb +18 -0
- metadata +85 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 529b8f0f4110561fd449bc25ce3cadb3527722934960c62bb70c379b78e6afc3
|
4
|
+
data.tar.gz: 1ebc1a337117b53b3ccee1def99b6d0494e23ea8d84aa91ad688bd8bad3feb35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8afc5349405c422bac83dfb7924db87a119c2b8749266a4ddcdc753904328c45f66559861ca1e460a4c329ed78681971de21b85343dc72fa1458ca8a41d0a94
|
7
|
+
data.tar.gz: 4348f9a542374f87b51a4bb54ff0ece4779c581374ef352ed707ec0e380e1ab4f9617159cb09c1ae4234dc79379138d74b3fa8be6c32ad0eb6cc9faec1af28f1
|
data/.rubocop.yml
CHANGED
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rmagick"
|
4
|
+
require "active_support/core_ext/enumerable"
|
5
|
+
|
6
|
+
module Qippet
|
7
|
+
module Boxes
|
8
|
+
# Abstract Class definiton for Box
|
9
|
+
class Box
|
10
|
+
attr_reader :node, :image
|
11
|
+
|
12
|
+
PADDING = 40
|
13
|
+
PADDING_X = 30
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
self.class.setup_attributes
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_child(child)
|
20
|
+
children << child
|
21
|
+
end
|
22
|
+
|
23
|
+
def render
|
24
|
+
construct_box_image
|
25
|
+
end
|
26
|
+
|
27
|
+
def children
|
28
|
+
@children ||= []
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_attributes(attributes_value)
|
32
|
+
allowed_attributes = attributes_value.filter { |key, _| self.class::ALLOWED_ATTRIBUTES.include?(key.to_sym) }
|
33
|
+
allowed_attributes.each { |key, val| instance_variable_set "@#{key}", val }
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.setup_attributes
|
37
|
+
return unless const_defined? "ALLOWED_ATTRIBUTES"
|
38
|
+
|
39
|
+
self::ALLOWED_ATTRIBUTES.each { |attr| attr_accessor attr }
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def construct_box_image
|
45
|
+
children_images = children.map(&:render)
|
46
|
+
height = image_height(children_images)
|
47
|
+
width = image_width(children_images)
|
48
|
+
@image = Magick::Image.new(width, height)
|
49
|
+
arrange_children_in_image(children_images)
|
50
|
+
end
|
51
|
+
|
52
|
+
def arrange_children_in_image(children_images)
|
53
|
+
acc = 10
|
54
|
+
children_images.each do |child_image|
|
55
|
+
image.composite! child_image, PADDING_X, acc, Magick::OverCompositeOp
|
56
|
+
acc += child_image.rows
|
57
|
+
end
|
58
|
+
image
|
59
|
+
end
|
60
|
+
|
61
|
+
def image_height(children_images)
|
62
|
+
children_images.sum(&:columns) + PADDING
|
63
|
+
end
|
64
|
+
|
65
|
+
def image_width(children_images)
|
66
|
+
(children_images.max { |a, b| a.rows <=> b.rows }&.rows || 0) + PADDING
|
67
|
+
end
|
68
|
+
|
69
|
+
# def get_image
|
70
|
+
# throw :undefined, "Box Abstract Method not defined"
|
71
|
+
# end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./box"
|
4
|
+
require "qippet/extract"
|
5
|
+
require "qippet/colors"
|
6
|
+
require "rouge"
|
7
|
+
|
8
|
+
module Qippet
|
9
|
+
module Boxes
|
10
|
+
# Box for creating code
|
11
|
+
class CodeBox < Box
|
12
|
+
# Class to formate code tokens
|
13
|
+
class BareColorFormatter < Rouge::Formatter
|
14
|
+
def stream(tokens)
|
15
|
+
token_lines(tokens).with_index do |line_tokens, lineno|
|
16
|
+
line_tokens_with_color = line_tokens.map do |token|
|
17
|
+
token_color = Colors.get_color_by_shortname(token[0].shortname)
|
18
|
+
[token_color, *token]
|
19
|
+
end
|
20
|
+
|
21
|
+
yield line_tokens_with_color, lineno
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :code_content, :code_lines, :show_line, :writer, :tokenizer, :formatter
|
27
|
+
|
28
|
+
ALLOWED_ATTRIBUTES = %i[source path range].freeze
|
29
|
+
|
30
|
+
TAB_SPACE = 20
|
31
|
+
CODE_LINE_HEIGHT = 19
|
32
|
+
CODE_CHAR_WIDTH = 8
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
super
|
36
|
+
@show_line = false
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_child(*)
|
40
|
+
throw :no_children_for_node, "Code Box cannot contain chidren"
|
41
|
+
end
|
42
|
+
|
43
|
+
def render
|
44
|
+
fetch_code_from_source
|
45
|
+
extract_line_range
|
46
|
+
return nil if code_content.nil?
|
47
|
+
|
48
|
+
setup_syntax_highliter
|
49
|
+
setup_writer
|
50
|
+
super
|
51
|
+
write_code_on_image
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def fetch_code_from_source
|
57
|
+
@code_content = source == "github" ? Extract.from_github(path) : Extract.from_file(path)
|
58
|
+
return nil if code_content.nil?
|
59
|
+
|
60
|
+
@code_lines = code_content.split("\n")
|
61
|
+
end
|
62
|
+
|
63
|
+
def extract_line_range
|
64
|
+
return if range.nil?
|
65
|
+
|
66
|
+
ranges = range.split(",").first(2).map { |val| Integer(val.strip) }
|
67
|
+
@code_lines = @code_lines[(ranges[0] || 0)..(ranges[1] || @code_lines.length)]
|
68
|
+
end
|
69
|
+
|
70
|
+
def write_code_on_image
|
71
|
+
@row_position = PADDING + CODE_LINE_HEIGHT
|
72
|
+
|
73
|
+
formatter.format(tokenizer.lex(code_lines.join("\n"))) do |line, index|
|
74
|
+
@column_position = PADDING
|
75
|
+
add_line_number(index + 1) if show_line
|
76
|
+
|
77
|
+
line.each do |token_with_color|
|
78
|
+
write_token(token_with_color)
|
79
|
+
end
|
80
|
+
@row_position += CODE_LINE_HEIGHT
|
81
|
+
end
|
82
|
+
image
|
83
|
+
end
|
84
|
+
|
85
|
+
def write_token(token_with_color)
|
86
|
+
color, token_type, token = token_with_color
|
87
|
+
token.prepend("\\") if token_type.shortname.blank?
|
88
|
+
|
89
|
+
escape_special_characters(token)
|
90
|
+
token_width = writer.get_type_metrics(token).width
|
91
|
+
|
92
|
+
writer.annotate(image, 0, 0, @column_position, @row_position, token) { |opt| opt.fill = color }
|
93
|
+
@column_position += token_width
|
94
|
+
end
|
95
|
+
|
96
|
+
def setup_syntax_highliter
|
97
|
+
@tokenizer = Rouge::Lexers::Ruby.new
|
98
|
+
@formatter = BareColorFormatter.new
|
99
|
+
end
|
100
|
+
|
101
|
+
def escape_special_characters(str)
|
102
|
+
str.gsub!(/%/, "%" => "%%")
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_line_number(number)
|
106
|
+
writer.annotate(image, 0, 0, @column_position, @row_position, number.to_s) { |opt| opt.fill = "black" }
|
107
|
+
@column_position += TAB_SPACE
|
108
|
+
end
|
109
|
+
|
110
|
+
def add_line_indentation(line)
|
111
|
+
line.chars.each do |c|
|
112
|
+
break if c != " "
|
113
|
+
|
114
|
+
@row_position += CODE_CHAR_WIDTH
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def setup_writer
|
119
|
+
@writer = Magick::Draw.new
|
120
|
+
@writer.font_family = "helvetica"
|
121
|
+
@writer.pointsize = 15
|
122
|
+
end
|
123
|
+
|
124
|
+
def image_height(*)
|
125
|
+
(code_lines.length * CODE_LINE_HEIGHT) + (2 * PADDING)
|
126
|
+
end
|
127
|
+
|
128
|
+
def image_width(*)
|
129
|
+
code_lines.map { |line| line.blank? ? 0 : writer.get_type_metrics(line).width }.max + (2 * PADDING)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
require "qippet/boxes/container_box"
|
5
|
+
require "qippet/boxes/text_box"
|
6
|
+
require "qippet/boxes/code_box"
|
7
|
+
|
8
|
+
module Qippet
|
9
|
+
module Builder
|
10
|
+
# Builder from XML content to box
|
11
|
+
module Xml
|
12
|
+
extend ActiveSupport::Autoload
|
13
|
+
extend self
|
14
|
+
|
15
|
+
def build(content = "")
|
16
|
+
structured_content = create_structure_from_xml_content(content)
|
17
|
+
return nil if structured_content.nil?
|
18
|
+
|
19
|
+
setup_node(structured_content[:root])
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def create_structure_from_xml_content(content)
|
25
|
+
return nil if content.empty?
|
26
|
+
|
27
|
+
parsed_data = Nokogiri::XML.parse(content)
|
28
|
+
parsed_data.deconstruct_keys([:root])
|
29
|
+
end
|
30
|
+
|
31
|
+
def setup_node(node)
|
32
|
+
return nil unless node.presence
|
33
|
+
|
34
|
+
node.content.strip!
|
35
|
+
node_box = get_node_box(node.name.presence)
|
36
|
+
add_box_attributes(node_box, node.attribute_nodes)
|
37
|
+
return node_box if node.children.empty?
|
38
|
+
|
39
|
+
node.children.each do |child|
|
40
|
+
box = setup_node(child)
|
41
|
+
node_box.add_child box if box.present?
|
42
|
+
end
|
43
|
+
|
44
|
+
node_box
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_box_attributes(box, attribute_nodes)
|
48
|
+
box.add_attributes(attribute_nodes.map { |attr| [attr.name, attr.value] })
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_node_box(node_name)
|
52
|
+
case node_name
|
53
|
+
when "q"
|
54
|
+
Qippet::Boxes::ContainerBox.new
|
55
|
+
when "code"
|
56
|
+
Qippet::Boxes::CodeBox.new
|
57
|
+
when "text"
|
58
|
+
Qippet::Boxes::TextBox.new
|
59
|
+
else
|
60
|
+
throw :unrecognize_structure, "Structure contains unrecognize element."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/core_ext/object/blank"
|
5
|
+
|
6
|
+
# Qippet
|
7
|
+
module Qippet
|
8
|
+
extend ActiveSupport::Autoload
|
9
|
+
|
10
|
+
# Builder Class
|
11
|
+
module Builder
|
12
|
+
extend ActiveSupport::Autoload
|
13
|
+
|
14
|
+
autoload :Xml
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def build(content)
|
18
|
+
Xml.build(content)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Qippet
|
4
|
+
# Module contains all colors for code
|
5
|
+
module Colors
|
6
|
+
DEFAULT_COLORS = {
|
7
|
+
w: "red",
|
8
|
+
o: "black",
|
9
|
+
nb: "blue",
|
10
|
+
c1: "gray",
|
11
|
+
s2: "#FF7F50",
|
12
|
+
no: "#50D0FF"
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def self.get_color_by_shortname(shortname)
|
16
|
+
DEFAULT_COLORS.fetch(shortname.to_sym, "black")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
require "net/http"
|
5
|
+
require "base64"
|
6
|
+
require "json"
|
7
|
+
require "pathname"
|
8
|
+
|
9
|
+
module Qippet
|
10
|
+
# Extract Class
|
11
|
+
class Extract
|
12
|
+
class << self
|
13
|
+
GITHUB_BASE_API_URL = "https://api.github.com/repos/"
|
14
|
+
|
15
|
+
def from_file(path)
|
16
|
+
path = Pathname.new(Dir.pwd).join(path)
|
17
|
+
read_file(path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def from_github(path = "rails/rails/contents/version.rb")
|
21
|
+
url = URI("#{GITHUB_BASE_API_URL}#{path}")
|
22
|
+
response = Net::HTTP.get_response(url)
|
23
|
+
response = JSON.parse(response.body)
|
24
|
+
Base64.decode64(response["content"])
|
25
|
+
rescue StandardError
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def read_file(path)
|
32
|
+
File.read(path)
|
33
|
+
rescue Errno::ENOENT, Errno::EISDIR
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/qippet/version.rb
CHANGED
data/lib/qippet.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support"
|
3
4
|
require_relative "qippet/version"
|
4
5
|
|
5
6
|
# Main object for Qippet. This is the main entry point for
|
6
7
|
# the application.
|
7
8
|
module Qippet
|
9
|
+
extend ActiveSupport::Autoload
|
10
|
+
|
11
|
+
# autoload :Xml, "qippet/extraction/xml"
|
12
|
+
autoload :Extract
|
13
|
+
autoload :Builder
|
8
14
|
class Error < StandardError; end
|
9
15
|
|
10
16
|
# Configuration class Object
|
@@ -27,5 +33,17 @@ module Qippet
|
|
27
33
|
def configure
|
28
34
|
yield configuration
|
29
35
|
end
|
36
|
+
|
37
|
+
def generate(path, output_file)
|
38
|
+
yield configuration if block_given?
|
39
|
+
|
40
|
+
extracted = Extract.from_file(path)
|
41
|
+
node = Builder.build(extracted)
|
42
|
+
puts "File does not contain a valid Qippet structure" if node.nil?
|
43
|
+
|
44
|
+
node&.render
|
45
|
+
# result.write(output_file)
|
46
|
+
output_file
|
47
|
+
end
|
30
48
|
end
|
31
49
|
end
|
metadata
CHANGED
@@ -1,15 +1,89 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qippet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Unegbu Kingsley
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
12
|
-
dependencies:
|
11
|
+
date: 2024-05-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.1'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 7.1.3.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '7.1'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 7.1.3.2
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: nokogiri
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.16'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.16.5
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.16'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.16.5
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rmagick
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '5.5'
|
60
|
+
type: :runtime
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '5.5'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: rouge
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '4.2'
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 4.2.1
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '4.2'
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 4.2.1
|
13
87
|
description: Create beautify code snippet image.
|
14
88
|
email: kingsobino@gmail.com
|
15
89
|
executables: []
|
@@ -24,6 +98,14 @@ files:
|
|
24
98
|
- README.md
|
25
99
|
- Rakefile
|
26
100
|
- lib/qippet.rb
|
101
|
+
- lib/qippet/boxes/box.rb
|
102
|
+
- lib/qippet/boxes/code_box.rb
|
103
|
+
- lib/qippet/boxes/container_box.rb
|
104
|
+
- lib/qippet/boxes/text_box.rb
|
105
|
+
- lib/qippet/builder.rb
|
106
|
+
- lib/qippet/builder/xml.rb
|
107
|
+
- lib/qippet/colors.rb
|
108
|
+
- lib/qippet/extract.rb
|
27
109
|
- lib/qippet/version.rb
|
28
110
|
- sig/qippet.rbs
|
29
111
|
homepage: https://github.com/Urchmaney/qippet
|