fancy 0.3.0

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.
Files changed (242) hide show
  1. data/AUTHORS +7 -0
  2. data/LICENSE +19 -0
  3. data/README +173 -0
  4. data/Rakefile +255 -0
  5. data/bin/fancy +40 -0
  6. data/bin/fdoc +23 -0
  7. data/bin/fyi +22 -0
  8. data/bin/ifancy +46 -0
  9. data/boot/README +12 -0
  10. data/boot/code_loader.rb +165 -0
  11. data/boot/compile.fy +3 -0
  12. data/boot/fancy_ext.rb +13 -0
  13. data/boot/fancy_ext/block_env.rb +29 -0
  14. data/boot/fancy_ext/class.rb +26 -0
  15. data/boot/fancy_ext/kernel.rb +12 -0
  16. data/boot/fancy_ext/module.rb +89 -0
  17. data/boot/fancy_ext/object.rb +34 -0
  18. data/boot/fancy_ext/string_helper.rb +10 -0
  19. data/boot/load.rb +72 -0
  20. data/boot/rbx-compiler/README +12 -0
  21. data/boot/rbx-compiler/compiler.rb +24 -0
  22. data/boot/rbx-compiler/compiler/ast.rb +23 -0
  23. data/boot/rbx-compiler/compiler/ast/README +11 -0
  24. data/boot/rbx-compiler/compiler/ast/array_literal.rb +13 -0
  25. data/boot/rbx-compiler/compiler/ast/assign.rb +57 -0
  26. data/boot/rbx-compiler/compiler/ast/block.rb +70 -0
  27. data/boot/rbx-compiler/compiler/ast/class_def.rb +35 -0
  28. data/boot/rbx-compiler/compiler/ast/expression_list.rb +57 -0
  29. data/boot/rbx-compiler/compiler/ast/hash_literal.rb +11 -0
  30. data/boot/rbx-compiler/compiler/ast/identifier.rb +120 -0
  31. data/boot/rbx-compiler/compiler/ast/match.rb +81 -0
  32. data/boot/rbx-compiler/compiler/ast/message_send.rb +71 -0
  33. data/boot/rbx-compiler/compiler/ast/method_def.rb +116 -0
  34. data/boot/rbx-compiler/compiler/ast/node.rb +6 -0
  35. data/boot/rbx-compiler/compiler/ast/range_literal.rb +22 -0
  36. data/boot/rbx-compiler/compiler/ast/require.rb +20 -0
  37. data/boot/rbx-compiler/compiler/ast/return.rb +29 -0
  38. data/boot/rbx-compiler/compiler/ast/ruby_args.rb +35 -0
  39. data/boot/rbx-compiler/compiler/ast/script.rb +56 -0
  40. data/boot/rbx-compiler/compiler/ast/singleton_method_def.rb +39 -0
  41. data/boot/rbx-compiler/compiler/ast/string_literal.rb +14 -0
  42. data/boot/rbx-compiler/compiler/ast/super.rb +25 -0
  43. data/boot/rbx-compiler/compiler/ast/try_catch_block.rb +220 -0
  44. data/boot/rbx-compiler/compiler/ast/tuple_literal.rb +33 -0
  45. data/boot/rbx-compiler/compiler/command.rb +39 -0
  46. data/boot/rbx-compiler/compiler/compiler.rb +83 -0
  47. data/boot/rbx-compiler/compiler/stages.rb +99 -0
  48. data/boot/rbx-compiler/parser.rb +2 -0
  49. data/boot/rbx-compiler/parser/README +15 -0
  50. data/boot/rbx-compiler/parser/Rakefile +54 -0
  51. data/boot/rbx-compiler/parser/extconf.rb +3 -0
  52. data/boot/rbx-compiler/parser/fancy_parser.bundle +0 -0
  53. data/boot/rbx-compiler/parser/fancy_parser.c +46 -0
  54. data/boot/rbx-compiler/parser/fancy_parser.h +8 -0
  55. data/boot/rbx-compiler/parser/lexer.lex +180 -0
  56. data/boot/rbx-compiler/parser/parser.rb +356 -0
  57. data/boot/rbx-compiler/parser/parser.y +711 -0
  58. data/boot/rsexp_pretty_printer.rb +76 -0
  59. data/doc/api/fancy.css +93 -0
  60. data/doc/api/fancy.jsonp +1 -0
  61. data/doc/api/fdoc.js +187 -0
  62. data/doc/api/index.html +57 -0
  63. data/doc/api/underscore-min.js +18 -0
  64. data/doc/features.md +228 -0
  65. data/examples/argv.fy +8 -0
  66. data/examples/arithmetic.fy +7 -0
  67. data/examples/armstrong_numbers.fy +33 -0
  68. data/examples/array.fy +52 -0
  69. data/examples/blocks.fy +15 -0
  70. data/examples/boolean.fy +24 -0
  71. data/examples/call_with_receiver.fy +9 -0
  72. data/examples/class.fy +68 -0
  73. data/examples/closures.fy +24 -0
  74. data/examples/constant_access.fy +15 -0
  75. data/examples/default_args.fy +17 -0
  76. data/examples/define_methods.fy +15 -0
  77. data/examples/documentation.fy +57 -0
  78. data/examples/documentation_formatters.fy +25 -0
  79. data/examples/echo.fy +16 -0
  80. data/examples/empty_catch.fy +4 -0
  81. data/examples/exception.fy +9 -0
  82. data/examples/factorial.fy +12 -0
  83. data/examples/fibonacci.fy +16 -0
  84. data/examples/files.fy +23 -0
  85. data/examples/finally.fy +5 -0
  86. data/examples/game_of_life.fy +148 -0
  87. data/examples/hashes.fy +7 -0
  88. data/examples/hello_world.fy +6 -0
  89. data/examples/html_generator.fy +54 -0
  90. data/examples/implicit_return.fy +3 -0
  91. data/examples/matchers.fy +6 -0
  92. data/examples/methods.fy +29 -0
  93. data/examples/nested_classes.fy +27 -0
  94. data/examples/nested_try.fy +9 -0
  95. data/examples/numbers.fy +12 -0
  96. data/examples/pattern_matching.fy +40 -0
  97. data/examples/person.fy +65 -0
  98. data/examples/project-euler/01.fy +8 -0
  99. data/examples/project-euler/02.fy +21 -0
  100. data/examples/project-euler/28.fy +33 -0
  101. data/examples/rbx/and_or.fy +7 -0
  102. data/examples/rbx/blocks.fy +22 -0
  103. data/examples/rbx/classes.fy +32 -0
  104. data/examples/rbx/hello.fy +8 -0
  105. data/examples/rbx/include.fy +12 -0
  106. data/examples/rbx/inherit.fy +11 -0
  107. data/examples/rbx/methods.fy +15 -0
  108. data/examples/rbx/nested_classes.fy +9 -0
  109. data/examples/rbx/require.fy +3 -0
  110. data/examples/rbx/strings.fy +5 -0
  111. data/examples/regex.fy +7 -0
  112. data/examples/require.fy +7 -0
  113. data/examples/retry.fy +12 -0
  114. data/examples/return.fy +13 -0
  115. data/examples/ruby_require.fy +7 -0
  116. data/examples/ruby_send.fy +3 -0
  117. data/examples/singleton_methods.fy +21 -0
  118. data/examples/stupid_quicksort.fy +12 -0
  119. data/examples/threads.fy +18 -0
  120. data/examples/tuple.fy +8 -0
  121. data/examples/webserver/webserver.fy +18 -0
  122. data/lib/argv.fy +36 -0
  123. data/lib/array.fy +207 -0
  124. data/lib/block.fy +88 -0
  125. data/lib/boot.fy +41 -0
  126. data/lib/class.fy +106 -0
  127. data/lib/compiler.fy +14 -0
  128. data/lib/compiler/ast.fy +40 -0
  129. data/lib/compiler/ast/assign.fy +96 -0
  130. data/lib/compiler/ast/block.fy +84 -0
  131. data/lib/compiler/ast/class_def.fy +33 -0
  132. data/lib/compiler/ast/expression_list.fy +47 -0
  133. data/lib/compiler/ast/identifier.fy +113 -0
  134. data/lib/compiler/ast/literals.fy +122 -0
  135. data/lib/compiler/ast/match.fy +88 -0
  136. data/lib/compiler/ast/message_send.fy +110 -0
  137. data/lib/compiler/ast/method_def.fy +90 -0
  138. data/lib/compiler/ast/node.fy +7 -0
  139. data/lib/compiler/ast/range.fy +16 -0
  140. data/lib/compiler/ast/require.fy +15 -0
  141. data/lib/compiler/ast/return.fy +23 -0
  142. data/lib/compiler/ast/script.fy +52 -0
  143. data/lib/compiler/ast/singleton_method_def.fy +35 -0
  144. data/lib/compiler/ast/super.fy +17 -0
  145. data/lib/compiler/ast/try_catch.fy +176 -0
  146. data/lib/compiler/ast/tuple_literal.fy +34 -0
  147. data/lib/compiler/command.fy +51 -0
  148. data/lib/compiler/compiler.fy +73 -0
  149. data/lib/compiler/stages.fy +81 -0
  150. data/lib/directory.fy +17 -0
  151. data/lib/documentation.fy +115 -0
  152. data/lib/enumerable.fy +269 -0
  153. data/lib/eval.fy +31 -0
  154. data/lib/fancy_spec.fy +202 -0
  155. data/lib/fdoc.fy +359 -0
  156. data/lib/fdoc_hook.fy +10 -0
  157. data/lib/file.fy +54 -0
  158. data/lib/hash.fy +56 -0
  159. data/lib/main.fy +80 -0
  160. data/lib/method.fy +22 -0
  161. data/lib/nil_class.fy +56 -0
  162. data/lib/number.fy +87 -0
  163. data/lib/object.fy +170 -0
  164. data/lib/package.fy +61 -0
  165. data/lib/package/dependency.fy +24 -0
  166. data/lib/package/installer.fy +180 -0
  167. data/lib/package/specification.fy +55 -0
  168. data/lib/package/uninstaller.fy +15 -0
  169. data/lib/parser.fy +4 -0
  170. data/lib/parser/ext/README +15 -0
  171. data/lib/parser/ext/ext.c +42 -0
  172. data/lib/parser/ext/ext.h +8 -0
  173. data/lib/parser/ext/extconf.rb +3 -0
  174. data/lib/parser/ext/lexer.lex +187 -0
  175. data/lib/parser/ext/parser.y +744 -0
  176. data/lib/parser/methods.fy +297 -0
  177. data/lib/rbx.fy +37 -0
  178. data/lib/rbx/array.fy +237 -0
  179. data/lib/rbx/bignum.fy +23 -0
  180. data/lib/rbx/block.fy +9 -0
  181. data/lib/rbx/class.fy +129 -0
  182. data/lib/rbx/code_loader.fy +192 -0
  183. data/lib/rbx/console.fy +63 -0
  184. data/lib/rbx/directory.fy +46 -0
  185. data/lib/rbx/documentation.fy +64 -0
  186. data/lib/rbx/environment_variables.fy +3 -0
  187. data/lib/rbx/exception.fy +30 -0
  188. data/lib/rbx/false_class.fy +58 -0
  189. data/lib/rbx/fiber.fy +25 -0
  190. data/lib/rbx/file.fy +191 -0
  191. data/lib/rbx/fixnum.fy +25 -0
  192. data/lib/rbx/float.fy +14 -0
  193. data/lib/rbx/hash.fy +38 -0
  194. data/lib/rbx/integer.fy +15 -0
  195. data/lib/rbx/io.fy +30 -0
  196. data/lib/rbx/match_data.fy +9 -0
  197. data/lib/rbx/method.fy +22 -0
  198. data/lib/rbx/name_error.fy +3 -0
  199. data/lib/rbx/no_method_error.fy +15 -0
  200. data/lib/rbx/object.fy +117 -0
  201. data/lib/rbx/range.fy +15 -0
  202. data/lib/rbx/regexp.fy +9 -0
  203. data/lib/rbx/string.fy +63 -0
  204. data/lib/rbx/symbol.fy +12 -0
  205. data/lib/rbx/system.fy +37 -0
  206. data/lib/rbx/tcp_server.fy +6 -0
  207. data/lib/rbx/tcp_socket.fy +7 -0
  208. data/lib/rbx/thread.fy +75 -0
  209. data/lib/rbx/tuple.fy +37 -0
  210. data/lib/set.fy +61 -0
  211. data/lib/stack.fy +51 -0
  212. data/lib/string.fy +58 -0
  213. data/lib/struct.fy +13 -0
  214. data/lib/symbol.fy +23 -0
  215. data/lib/true_class.fy +43 -0
  216. data/lib/tuple.fy +68 -0
  217. data/lib/version.fy +6 -0
  218. data/tests/argv.fy +13 -0
  219. data/tests/array.fy +343 -0
  220. data/tests/assignment.fy +53 -0
  221. data/tests/block.fy +103 -0
  222. data/tests/class.fy +409 -0
  223. data/tests/control_flow.fy +79 -0
  224. data/tests/documentation.fy +24 -0
  225. data/tests/exception.fy +115 -0
  226. data/tests/file.fy +86 -0
  227. data/tests/hash.fy +101 -0
  228. data/tests/method.fy +131 -0
  229. data/tests/nil_class.fy +55 -0
  230. data/tests/number.fy +128 -0
  231. data/tests/object.fy +125 -0
  232. data/tests/parsing/sexp.fy +50 -0
  233. data/tests/pattern_matching.fy +82 -0
  234. data/tests/range.fy +11 -0
  235. data/tests/set.fy +10 -0
  236. data/tests/stack.fy +22 -0
  237. data/tests/string.fy +102 -0
  238. data/tests/symbol.fy +17 -0
  239. data/tests/true_class.fy +63 -0
  240. data/tests/tuple.fy +21 -0
  241. data/tools/fancy-mode.el +63 -0
  242. metadata +321 -0
data/lib/eval.fy ADDED
@@ -0,0 +1,31 @@
1
+ require: "compiler"
2
+
3
+ def Fancy eval: code binding: bnd (nil) file: file ("(fancy-eval)") line: line (1) {
4
+ bnd if_nil: {
5
+ bnd = Binding setup(Rubinius VariableScope of_sender(),
6
+ Rubinius CompiledMethod of_sender(),
7
+ Rubinius StaticScope of_sender())
8
+ }
9
+
10
+ # The compiled method
11
+ cm = Fancy Compiler compile_code: code vars: (bnd variables()) file: file line: line
12
+ cm scope=(bnd static_scope() dup())
13
+ cm name=('__fancy_eval__)
14
+
15
+ script = Rubinius CompiledMethod Script new(cm, file, true)
16
+ script eval_binding=(bnd)
17
+ script eval_source=(code)
18
+
19
+ cm scope() script=(script)
20
+
21
+ be = Rubinius BlockEnvironment new()
22
+ be under_context(bnd variables(), cm)
23
+
24
+ if: (bnd from_proc?()) then: {
25
+ be proc_environment=(bnd proc_environment)
26
+ }
27
+
28
+ be from_eval!()
29
+
30
+ be call()
31
+ }
data/lib/fancy_spec.fy ADDED
@@ -0,0 +1,202 @@
1
+ class FancySpec {
2
+ """
3
+ The FancySpec class is used for defining FancySpec testsuites.
4
+ Have a look at the tests/ directory to see some examples.
5
+ """
6
+
7
+
8
+ def initialize: @description test_obj: @test_obj (@description) {
9
+ @spec_tests = []
10
+ }
11
+
12
+ def FancySpec describe: test_obj with: block {
13
+ spec = FancySpec new: test_obj
14
+ block call_with_receiver: spec
15
+ spec run
16
+ }
17
+
18
+ def FancySpec describe: description for: test_obj with: block {
19
+ spec = FancySpec new: description test_obj: test_obj
20
+ block call_with_receiver: spec
21
+ spec run
22
+ }
23
+
24
+ def it: spec_info_string when: spec_block {
25
+ test = SpecTest new: spec_info_string block: spec_block
26
+ @spec_tests << test
27
+ }
28
+
29
+ def it: spec_info_string for: method_name when: spec_block {
30
+ test = SpecTest new: spec_info_string block: spec_block
31
+ # try {
32
+ # @test_obj method: method_name . if_do: |method| {
33
+ # method tests << test
34
+ # }
35
+ # } catch MethodNotFoundError => e {
36
+ # # ignore errors
37
+ # }
38
+ @spec_tests << test
39
+ }
40
+
41
+ def run {
42
+ "Running tests for: " ++ @description ++ ": " print
43
+ @spec_tests each: |test| {
44
+ test run: @test_obj
45
+ }
46
+ # Console newline Console newline
47
+ # untested_methods = @test_obj methods select: |m| {
48
+ # m tests size == 0
49
+ # }
50
+ # untested_methods empty? if_false: {
51
+ # ["WARNING: These methods need tests:",
52
+ # untested_methods map: 'name . select: |m| { m whitespace? not } . join: ", "] println
53
+ # }
54
+ Console newline
55
+ }
56
+
57
+
58
+ class SpecTest {
59
+ @@failed_positive = []
60
+ @@failed_negative = []
61
+
62
+ def SpecTest failed_test: actual_and_expected {
63
+ @@failed_positive << actual_and_expected
64
+ }
65
+
66
+ def SpecTest failed_negative_test: value {
67
+ @@failed_negative << value
68
+ }
69
+
70
+ def initialize: @info_str block: @block {
71
+ { @@failed_positive = [] } unless: @@failed_positive
72
+ { @@failed_negative = [] } unless: @@failed_negative
73
+ }
74
+
75
+ def run: test_obj {
76
+ @@failed_positive = []
77
+ @@failed_negative = []
78
+ try {
79
+ @block call
80
+ } catch IOError => e {
81
+ SpecTest failed_test: [e, "UNKNOWN"]
82
+ }
83
+
84
+ any_failure = nil
85
+ if: (@@failed_positive size > 0) then: {
86
+ any_failure = true
87
+ Console newline
88
+ "> FAILED: " ++ test_obj ++ " " ++ @info_str print
89
+ self print_failed_positive
90
+ }
91
+
92
+ if: (@@failed_negative size > 0) then: {
93
+ any_failure = true
94
+ Console newline
95
+ "> FAILED: " ++ test_obj ++ " " ++ @info_str print
96
+ self print_failed_negative
97
+ }
98
+
99
+ { "." print } unless: any_failure
100
+ }
101
+
102
+ def print_failed_positive {
103
+ " [" ++ (@@failed_positive size) ++ " unexpected values]" println
104
+ "Got: " println
105
+ @@failed_positive each: |f| {
106
+ " " ++ (f first inspect) ++ " instead of: " ++ (f second inspect) println
107
+ }
108
+ }
109
+
110
+ def print_failed_negative {
111
+ " [" ++ (@@failed_negative size) ++ " unexpected values]" println
112
+ "Should not have gotten the following values: " println
113
+ @@failed_negative each: |f| {
114
+ " " ++ (f inspect) println
115
+ }
116
+ }
117
+ }
118
+
119
+ class PositiveMatcher {
120
+ """PositiveMatcher expects its actual value to be equal to an
121
+ expected value.
122
+ If the values are not equal, a SpecTest failure is generated."""
123
+
124
+ def initialize: @actual_value {
125
+ }
126
+
127
+ def == expected_value {
128
+ unless: (@actual_value == expected_value) do: {
129
+ SpecTest failed_test: [@actual_value, expected_value]
130
+ }
131
+ }
132
+
133
+ def != expected_value {
134
+ unless: (@actual_value != expected_value) do: {
135
+ SpecTest failed_negative_test: @actual_value
136
+ }
137
+ }
138
+
139
+ def unknown_message: msg with_params: params {
140
+ """Forwardy any other message and parameters to the object itself
141
+ and checks the return value."""
142
+
143
+ unless: (@actual_value send: msg params: params) do: {
144
+ SpecTest failed_test: [@actual_value, params first]
145
+ }
146
+ }
147
+
148
+ def be: block {
149
+ unless: (block call: [@actual_value]) do: {
150
+ SpecTest failed_test: [@actual_value, nil]
151
+ }
152
+ }
153
+ }
154
+
155
+ class NegativeMatcher {
156
+ """NegativeMatcher expects its actual value to be unequal to an
157
+ expected value.
158
+ If the values are equal, a SpecTest failure is generated."""
159
+
160
+ def initialize: @actual_value {
161
+ }
162
+
163
+ def == expected_value {
164
+ if: (@actual_value == expected_value) then: {
165
+ SpecTest failed_negative_test: @actual_value
166
+ }
167
+ }
168
+
169
+ def != expected_value {
170
+ if: (@actual_value != expected_value) then: {
171
+ SpecTest failed_test: [@actual_value, expected_value]
172
+ }
173
+ }
174
+
175
+ def unknown_message: msg with_params: params {
176
+ """Forwardy any other message and parameters to the object itself
177
+ and checks the return value."""
178
+
179
+ if: (@actual_value send: msg params: params) then: {
180
+ SpecTest failed_negative_test: @actual_value
181
+ }
182
+ }
183
+
184
+ def be: block {
185
+ if: (block call: [@actual_value]) then: {
186
+ SpecTest failed_negative_test: @actual_value
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ class Object {
193
+ def should {
194
+ "Returns a PositiveMatcher for self."
195
+ FancySpec PositiveMatcher new: self
196
+ }
197
+
198
+ def should_not {
199
+ "Returns a NegativeMatcher for self."
200
+ FancySpec NegativeMatcher new: self
201
+ }
202
+ }
data/lib/fdoc.fy ADDED
@@ -0,0 +1,359 @@
1
+ # Load all of fancy.
2
+ require: "boot"
3
+
4
+ class Fancy FDoc {
5
+ """
6
+
7
+ FDoc is a tool to generate API documentation from Fancy source.
8
+
9
+ Works as follows:
10
+
11
+ 1. We setup a handler to be invoked every time an object is set documentation
12
+ See fdoc_hook.fy, its loaded even before all of lib/rbx/*.fy so we can
13
+ Also have documentation for all fancy rubinius.
14
+ 2. We load boot.fy, so we get documentation for all fancy's lib.
15
+ 3. We run FDoc main
16
+ which can possibly load any file/directory you specify and optionally
17
+ run specs, effectively associating them with documented objects.
18
+ 4. Generate output file.
19
+ Currently the plan is to output a json formatted object.
20
+ To be loaded by an html file and use jquery to build a GUI from it.
21
+
22
+ """
23
+
24
+ OUTPUT_DIR = "doc/api/"
25
+
26
+ def self main {
27
+ """
28
+ FDoc will load all .fy files you give to it, and optionally run
29
+ any specified FancySpec, and later produce documentation output.
30
+ """
31
+
32
+ output_dir = OUTPUT_DIR
33
+ ARGV for_option: "-o" do: |d| { output_dir = d }
34
+ require("fileutils")
35
+ FileUtils mkdir_p(output_dir)
36
+
37
+ # Currently we just load any files given on ARGV.
38
+ ARGV each: |file| { Fancy CodeLoader load_compiled_file(file) }
39
+
40
+ # by now simply produce a apidoc/fancy.jsonp file.
41
+ json = JSON new: @documented_objects
42
+ json write: (File expand_path("fancy.jsonp", output_dir))
43
+
44
+ ["Open your browser at " ++ output_dir ++ "index.html.",
45
+ " " ++ (json classes size) ++ " classes. ",
46
+ " " ++ (json methods size) ++ " methods. ",
47
+ " " ++ (json objects size) ++ " other objects. "] println
48
+ }
49
+
50
+
51
+ class JSON {
52
+
53
+ read_slots: ['classes, 'methods, 'blocks, 'objects]
54
+
55
+ def initialize: documented {
56
+ @documented_objects = documented
57
+
58
+ is_class = |o| { o kind_of?: Module }
59
+ is_method = |o| { o kind_of?: Rubinius CompiledMethod }
60
+ is_block = |o| { o kind_of?: Rubinius BlockEnvironment }
61
+ all_other = |o| {
62
+ [is_class, is_method, is_block] all?() |b| { b call: [o] == false }
63
+ }
64
+
65
+ @classes = @documented_objects keys select: is_class
66
+ @methods = @documented_objects keys select: is_method
67
+ @blocks = @documented_objects keys select: is_block
68
+ @objects = @documented_objects keys select: all_other
69
+ }
70
+
71
+ def string_to_json: obj { obj to_s inspect }
72
+ def symbol_to_json: obj { obj to_s }
73
+
74
+ def array_to_json: obj {
75
+ str = ["["]
76
+ obj each: |i| { str << $ to_json: i } in_between: { str << ", " }
77
+ str << ["]"]
78
+ str join
79
+ }
80
+
81
+ def hash_to_json: obj {
82
+ str = ["{"]
83
+ keys = obj keys
84
+ keys each: |i| {
85
+ str << $ to_json: i
86
+ str << ":"
87
+ str << $ to_json: (obj at: i)
88
+ } in_between: { str << ", " }
89
+ str << "}"
90
+ str join
91
+ }
92
+
93
+ def to_json: obj {
94
+ # Reimplement now we have pattern matching dispatch.
95
+ match obj -> {
96
+ case Hash -> self hash_to_json: obj
97
+ case Array -> self array_to_json: obj
98
+ case Symbol -> self symbol_to_json: obj
99
+ case String -> self string_to_json: obj
100
+ case Numeric -> obj
101
+ case nil -> "null"
102
+ case _ -> "Dont know how to convert " ++ (obj inspect) ++ " to JSON" raise!
103
+ }
104
+ }
105
+
106
+ def popuplate_methods: cls on: attr type: type known: methods {
107
+ cls send(type ++ "s", false) each: |n| {
108
+ mattr = <[]>
109
+ exec = cls send(type, n) executable()
110
+ methods delete(exec)
111
+ mdoc = Fancy Documentation for: exec
112
+ if: mdoc then: {
113
+ mattr at: 'doc put: $ mdoc format: 'fdoc
114
+ if: (mdoc meta) then: {
115
+ mattr at: 'arg put: $ mdoc meta at: 'argnames
116
+ }
117
+ }
118
+ if: (exec class() == Rubinius CompiledMethod) then: {
119
+ relative_file = exec file()
120
+ # HACK: We simply delete everything before lib/
121
+ # TODO: Fix, either use a -r (root) option or use __FILE__
122
+ relative_file = relative_file to_s gsub(/.*lib/, "lib")
123
+ lines = exec lines() to_a()
124
+ mattr at: 'file put: $ relative_file
125
+ # TODO calculate line numbers from compiled method
126
+ # right now we only use the first line of code in the body.
127
+ mattr at: 'lines put: $ [lines[3], lines[3]]
128
+ }
129
+ attr[(type ++ "s") intern()] at: n put: mattr
130
+ }
131
+ }
132
+
133
+ def generate_map {
134
+ map = <['title => "Fancy Documentation", 'date => Time now() to_s(),
135
+ 'classes => <[]>, 'methods => <[]>, 'objects => <[]> ]>
136
+
137
+ methods = @methods dup()
138
+
139
+ @classes each: |cls| {
140
+ name = cls name() gsub("::", " ")
141
+ doc = Fancy Documentation for: cls
142
+ attr = <[
143
+ 'doc => doc format: 'fdoc,
144
+ 'instance_methods => <[]>,
145
+ 'methods => <[]>,
146
+ 'ancestors => cls ancestors() map: |c| { c name() gsub("::", " ") }
147
+ ]>
148
+ popuplate_methods: cls on: attr type: 'instance_method known: methods
149
+ popuplate_methods: cls on: attr type: 'method known: methods
150
+ map['classes] at: name put: attr
151
+ }
152
+
153
+ methods each: |cm| {
154
+ cls = cm scope() module()
155
+ cls_name = cls name() gsub("::", " ")
156
+ cls_attr = map['classes] at: cls_name
157
+
158
+ full_name = cls_name ++ "#" ++ (cm name())
159
+
160
+ doc = Fancy Documentation for: cm
161
+ attr = <[
162
+ 'args => doc meta at: 'argnames,
163
+ 'doc => doc format: 'fdoc
164
+ ]>
165
+
166
+ map['methods] at: full_name put: attr
167
+ }
168
+
169
+ map
170
+ }
171
+
172
+
173
+ def write: filename call: name ("fancy.fdoc") {
174
+ map = self generate_map
175
+ json = self to_json: map
176
+ js = "(function() { " ++ name ++ "(" ++ json ++ "); })();"
177
+ File open: filename modes: ['write] with: |out| { out print: js }
178
+ }
179
+
180
+ }
181
+
182
+
183
+ class Formatter {
184
+ """
185
+ A documentation formater intended to be used by @FDoc@.
186
+
187
+ This formatter makes some transformations on a docstring
188
+ and then converts it to html using markdown.
189
+ """
190
+
191
+ Fancy Documentation formatter: 'fdoc is: |d| { format: d }
192
+
193
+
194
+ def self format: doc {
195
+ str = doc to_s
196
+ tags = <[ ]>
197
+ str = remove_indentation: str
198
+ str = remove_tags: str into: tags
199
+ str = create_tags: str with: tags
200
+ str = create_class_references: str
201
+ str = create_method_references: str
202
+ str = create_code: str
203
+ str = htmlize: str
204
+ str
205
+ }
206
+
207
+ def self remove_indentation: str {
208
+ """
209
+ Remove leading white space for multi-line strings.
210
+ This method expects the first character to be an line return.
211
+ """
212
+ m = /^(\r?\n)*(\s+)/ match(str)
213
+ str = str strip()
214
+ if: m then: {
215
+ pattern = "^ {" ++ (m[2] size()) ++ "}"
216
+ rex = Regexp.new(pattern)
217
+ str = str gsub(rex, "");
218
+ }
219
+ str
220
+ }
221
+
222
+ def self create_class_references: str {
223
+ """
224
+ Creates class references for Fancy class names.
225
+
226
+ A docstring may contain class names sorounded by @
227
+ without space between the @.
228
+
229
+ Nested classes can be indicated by using :: like
230
+
231
+ Foo::Bar
232
+
233
+ This will create references for both, @Foo and @Bar
234
+
235
+ Instance methods should be written:
236
+
237
+ Foo::Bar#baz
238
+
239
+ Class methods should be written:
240
+
241
+ Foo::Bar.baz
242
+
243
+ Some examples:
244
+
245
+ A simple class reference:
246
+
247
+ @Fancy@
248
+
249
+ Nested class reference:
250
+
251
+ @Fancy::FDoc@
252
+
253
+ A fancy method without arguments:
254
+
255
+ @Fancy::FDoc::JSON#:generate_map@
256
+
257
+ A ruby method reference (will link to ruby docs if available)
258
+
259
+ @String#split@
260
+
261
+ A fancy method with many arguments:
262
+
263
+ @Fancy::Package::Installer#initialize:version:install_path:@
264
+
265
+ A singleton method:
266
+
267
+ @Fancy::FDoc::Formatter~format:@
268
+
269
+ """
270
+ str gsub(/@[A-Z][^\r\n\s]+?@/) |cstr| {
271
+ names = cstr slice(1, cstr size() - 2) split("::")
272
+ refs = []
273
+ names each_with_index() |name, idx| {
274
+ n = name split(/[\#\~]/)
275
+ clas = names take(idx) + [n[0]] . join(" ")
276
+ html = "<code data-lang=\"fancy\" data-class-ref=\"" ++ .
277
+ clas ++ "\" class=\"class-ref selectable\">" ++ (n[0]) ++ "</code>"
278
+ refs << html
279
+
280
+ # Generate a reference for last method if availble.
281
+ if: (n[1]) then: {
282
+ method = n[1]
283
+ if: (method start_with?(":")) then: {
284
+ method = method sub(/^:/, "")
285
+ }
286
+ sigil = ""
287
+ name =~ (Regexp.new("^#")) . if_do: { sigil = "<small>#</small>" }
288
+ type = n[1] include?(":") . if_do: {
289
+ if: (sigil == "") then: {
290
+ "singleton-method-ref"
291
+ } else: {
292
+ "instance-method-ref"
293
+ }
294
+ } else: {
295
+ if: (sigil == "") then: {
296
+ "ruby-singleton-method-ref"
297
+ } else: {
298
+ "ruby-instance-method-ref"
299
+ }
300
+ }
301
+
302
+ html = sigil ++ "<code data-lang=\"fancy\" data-" ++ type ++ "=\"" ++ .
303
+ (n[1]) ++ "\" " ++ " data-owner-class=\"" ++ clas ++ "\" " ++ .
304
+ "class=\"" ++ type ++ " selectable\">" ++ method ++ "</code>"
305
+ refs << html
306
+ }
307
+ }
308
+ refs join(" ")
309
+ }
310
+ }
311
+
312
+ def self remove_tags: str into: map {
313
+ ary = str split(/\r?\n/) map: |line| {
314
+ m = /^@([a-z@]\S+?)\s+(.*)/ match(line)
315
+ if: m then: {
316
+ map at: (m[1]) put: (m[2])
317
+ nil
318
+ } else: {
319
+ line
320
+ }
321
+ }
322
+ ary compact join("\n")
323
+ }
324
+
325
+ def self create_tags: str with: map {
326
+ tags = map map: |pair| {
327
+ name = pair[0]
328
+ value = pair[1]
329
+ "<div class=\"doctag\"><label> @" ++ name ++ .
330
+ " </label><div>" ++ value ++ "</div></div>"
331
+ }
332
+ str ++ "\n<div class=\"doctags\">" ++ (tags join()) ++ "</div>"
333
+ }
334
+
335
+ def self create_code: str {
336
+ str gsub(/@([^\s,\]\)\}\.]+)/,
337
+ "<code data-lang=\"fancy\">\\1</code>")
338
+ }
339
+
340
+ def self htmlize: str {
341
+ require("rubygems")
342
+ require("rdiscount")
343
+ RDiscount new(str) to_html()
344
+ }
345
+
346
+ def self create_method_references: str {
347
+ # First methods ending with :
348
+ str gsub(/@([a-z_:]+:)@/,
349
+ "<code data-lang=\"fancy\" data-method=\"\\1\" class=\"selectable\">\\1</code>") .
350
+ # fancy methods starting with : (argless fancy methods)
351
+ gsub(/@:([a-z_]+)@/,
352
+ "<code data-lang=\"fancy\" data-method=\":\\1\" class=\"selectable\">\\1</code>")
353
+ }
354
+
355
+ }
356
+
357
+ }
358
+
359
+ Fancy FDoc main