+(defparameter *proto-name-separators* '(#\- #\_ #\/ #\space))
+(defparameter *camel-case-field-names* nil)
+
+(defun find-proto-package (name)
+ "A very fuzzy definition of 'find-package'."
+ (typecase name
+ ((or string symbol)
+ ;; Try looking under the given name and the all-uppercase name
+ (or (find-package (string name))
+ (find-package (string-upcase (string name)))))
+ (cons
+ ;; If 'name' is a list, it's actually a fully-qualified path
+ (or (find-proto-package (first name))
+ (find-proto-package (format nil "~{~A~^.~}" name))))))
+
+;; "class-name" -> "ClassName", ("ClassName")
+;; "outer-class.inner-class" -> "InnerClass", ("OuterClass" "InnerClass")
+(defun class-name->proto (x)
+ "Given a Lisp class name, returns a Protobufs message or enum name.
+ The second value is the fully qualified name, as a list."
+ (let* ((xs (split-string (string x) :separators '(#\.)))
+ (ns (loop for x in (butlast xs)
+ collect (remove-if-not #'alphanumericp
+ (camel-case (format nil "~A" x) *proto-name-separators*))))
+ (nx (car (last xs)))
+ (name (remove-if-not #'alphanumericp (camel-case nx *proto-name-separators*))))
+ (values name (append ns (list name))
+ ;; This might be the name of a package, too
+ (format nil "~{~A~^.~}" (butlast xs)))))
+
+;; "enum-value" -> "ENUM_VALUE", ("ENUM_VALUE")
+;; "class-name.enum-value" -> "ENUM_VALUE", ("ClassName" "ENUM_VALUE")
+(defun enum-name->proto (x &optional prefix)
+ "Given a Lisp enum value name, returns a Protobufs enum value name.
+ The second value is the fully qualified name, as a list."
+ (let* ((xs (split-string (string x) :separators '(#\.)))
+ (ns (loop for x in (butlast xs)
+ collect (remove-if-not #'alphanumericp
+ (camel-case (format nil "~A" x) *proto-name-separators*))))
+ (nx (string-upcase (car (last xs))))
+ (nx (if (and prefix (starts-with nx prefix)) (subseq nx (length prefix)) nx))
+ ;; Keep underscores, they are standards separators in Protobufs enum names
+ (name (remove-if-not #'(lambda (x) (or (alphanumericp x) (eql x #\_)))
+ (format nil "~{~A~^_~}"
+ (split-string nx :separators *proto-name-separators*)))))
+ (values name (append ns (list name))
+ (format nil "~{~A~^.~}" (butlast xs)))))
+
+;; "slot-name" -> "slot_name", ("slot_name") or "slotName", ("slotName")
+;; "class-name.slot-name" -> "Class.slot_name", ("ClassName" "slot_name")
+(defun slot-name->proto (x)
+ "Given a Lisp slot name, returns a Protobufs field name.
+ The second value is the fully qualified name, as a list."
+ (let* ((xs (split-string (string x) :separators '(#\.)))
+ (ns (loop for x in (butlast xs)
+ collect (remove-if-not #'alphanumericp
+ (camel-case (format nil "~A" x) *proto-name-separators*))))
+ (nx (string-downcase (car (last xs))))
+ (name (if *camel-case-field-names*
+ (remove-if-not #'alphanumericp
+ (camel-case-but-one (format nil "~A" nx) *proto-name-separators*))
+ ;; Keep underscores, they are standards separators in Protobufs field names
+ (remove-if-not #'(lambda (x) (or (alphanumericp x) (eql x #\_)))
+ (format nil "~{~A~^_~}"
+ (split-string nx :separators *proto-name-separators*))))))
+ (values name (append ns (list name))
+ (format nil "~{~A~^.~}" (butlast xs)))))
+
+
+;; "ClassName" -> 'class-name
+;; "cl-user.ClassName" -> 'cl-user::class-name
+;; "cl-user.OuterClass.InnerClass" -> 'cl-user::outer-class.inner-class
+(defun proto->class-name (x &optional package)
+ "Given a Protobufs message or enum type name, returns a Lisp class or type name.
+ This resolves Protobufs qualified names as best as it can."
+ (let* ((xs (split-string (substitute #\- #\_ (uncamel-case x))
+ :separators '(#\.)))
+ (pkg1 (and (cdr xs) (find-proto-package (first xs))))
+ (pkgn (and (cdr xs) (find-proto-package (butlast xs))))
+ (package (or pkg1 pkgn package))
+ (name (format nil "~{~A~^.~}" (if pkg1 (cdr xs) (if pkgn (last xs) xs)))))
+ (values (if package (intern name package) (make-symbol name)) package xs
+ ;; This might be the name of a package, too
+ (format nil "~{~A~^.~}" (butlast xs)))))
+
+;; "ENUM_VALUE" -> :enum-value
+;; "cl-user.ENUM_VALUE" -> :enum-value
+;; "cl-user.OuterClass.ENUM_VALUE" -> :enum-value
+(defun proto->enum-name (x &optional package)
+ "Given a Protobufs enum value name, returns a Lisp enum value name.
+ This resolves Protobufs qualified names as best as it can."
+ (let* ((xs (split-string (substitute #\- #\_ (uncamel-case x))
+ :separators '(#\.)))
+ (pkg1 (and (cdr xs) (find-proto-package (first xs))))
+ (pkgn (and (cdr xs) (find-proto-package (butlast xs))))
+ (package (or pkg1 pkgn package))
+ (name (format nil "~{~A~^.~}" (if pkg1 (cdr xs) (if pkgn (last xs) xs)))))
+ (values (kintern name) package xs
+ (format nil "~{~A~^.~}" (butlast xs)))))
+
+;; "slot_name" or "slotName" -> 'slot-name
+;; "cl-user.slot_name" or "cl-user.slotName" -> 'cl-user::slot-name
+;; "cl-user.OuterClass.slot_name" -> 'cl-user::outer-class.slot-name
+(defun proto->slot-name (x &optional package)
+ "Given a Protobufs field value name, returns a Lisp slot name.
+ This resolves Protobufs qualified names as best as it can."
+ (let* ((xs (split-string (substitute #\- #\_ (uncamel-case x))
+ :separators '(#\.)))
+ (pkg1 (and (cdr xs) (find-proto-package (first xs))))
+ (pkgn (and (cdr xs) (find-proto-package (butlast xs))))
+ (package (or pkg1 pkgn package))
+ (name (format nil "~{~A~^.~}" (if pkg1 (cdr xs) (if pkgn (last xs) xs)))))
+ (values (if package (intern name package) (make-symbol name)) package xs
+ (format nil "~{~A~^.~}" (butlast xs)))))